Skip to content

Commit 234db3d

Browse files
committed
add tabs to admin page
1 parent 30402c2 commit 234db3d

4 files changed

Lines changed: 136 additions & 198 deletions

File tree

src/pages/AdminPage.tsx

Lines changed: 70 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ import {
99
ToastContainer,
1010
Toast,
1111
Alert,
12+
Tabs,
13+
Tab,
1214
} from 'react-bootstrap';
1315
import { useStore } from '../stores/storeConfig';
16+
import UserGrantsTab from '../components/admin/UserGrantsTab';
17+
import './AdminPage.css';
18+
19+
type AdminTab = 'grant-users' | 'grants';
1420

1521
const AdminPage: React.FC = observer(() => {
1622
const { grantStore } = useStore();
@@ -21,18 +27,12 @@ const AdminPage: React.FC = observer(() => {
2127
errorMessage,
2228
fetchGrants,
2329
createGrant,
24-
deleteGrant,
25-
assignGrantToUser,
26-
removeGrantFromUser,
2730
clearMessages,
2831
} = grantStore;
2932

33+
const [activeTab, setActiveTab] = React.useState<AdminTab>('grant-users');
3034
const [createName, setCreateName] = React.useState('');
3135
const [createDescription, setCreateDescription] = React.useState('');
32-
const [assignEmail, setAssignEmail] = React.useState('');
33-
const [assignGrant, setAssignGrant] = React.useState('');
34-
const [removeEmail, setRemoveEmail] = React.useState('');
35-
const [removeGrant, setRemoveGrant] = React.useState('');
3636

3737
useEffect(() => {
3838
fetchGrants();
@@ -45,155 +45,79 @@ const AdminPage: React.FC = observer(() => {
4545
}
4646
}, [successMessage, errorMessage, clearMessages]);
4747

48-
useEffect(() => {
49-
if (grants.length > 0 && !assignGrant) {
50-
setAssignGrant(grants[0].name);
51-
}
52-
if (grants.length > 0 && !removeGrant) {
53-
setRemoveGrant(grants[0].name);
54-
}
55-
}, [grants, assignGrant, removeGrant]);
56-
5748
const handleCreateGrant = async (e: React.FormEvent) => {
5849
e.preventDefault();
5950
await createGrant({ name: createName.trim(), description: createDescription.trim() });
6051
setCreateName('');
6152
setCreateDescription('');
6253
};
6354

64-
const handleAssignGrant = async (e: React.FormEvent) => {
65-
e.preventDefault();
66-
await assignGrantToUser({ email: assignEmail.trim(), grant: assignGrant });
67-
setAssignEmail('');
68-
};
69-
70-
const handleRemoveGrant = async (e: React.FormEvent) => {
71-
e.preventDefault();
72-
await removeGrantFromUser({ email: removeEmail.trim(), grant: removeGrant });
73-
setRemoveEmail('');
74-
};
75-
76-
const handleDeleteGrant = async (grantName: string) => {
77-
if (window.confirm(`Delete grant "${grantName}"? This will remove all user associations.`)) {
78-
await deleteGrant(grantName);
79-
}
80-
};
81-
8255
return (
83-
<Container className='py-4'>
56+
<Container className='py-4 admin-page' dir='ltr'>
8457
<h2 className='mb-4'>Grant Management</h2>
8558

86-
<h4 className='mb-3'>Grants</h4>
87-
{loading ? (
88-
<Spinner animation='border' />
89-
) : grants.length === 0 ? (
90-
<Alert variant='info'>No grants found.</Alert>
91-
) : (
92-
<Table striped bordered hover responsive className='mb-4'>
93-
<thead>
94-
<tr>
95-
<th>Name</th>
96-
<th>Description</th>
97-
<th>Actions</th>
98-
</tr>
99-
</thead>
100-
<tbody>
101-
{grants.map((grant) => (
102-
<tr key={grant.id}>
103-
<td>{grant.name}</td>
104-
<td>{grant.description}</td>
105-
<td>
106-
<Button
107-
variant='danger'
108-
size='sm'
109-
onClick={() => handleDeleteGrant(grant.name)}
110-
>
111-
Delete
112-
</Button>
113-
</td>
114-
</tr>
115-
))}
116-
</tbody>
117-
</Table>
118-
)}
119-
120-
<h4 className='mb-3'>Create Grant</h4>
121-
<Form onSubmit={handleCreateGrant} className='mb-4'>
122-
<Form.Group className='mb-3'>
123-
<Form.Label>Name</Form.Label>
124-
<Form.Control
125-
type='text'
126-
value={createName}
127-
onChange={(e) => setCreateName(e.target.value)}
128-
required
129-
/>
130-
</Form.Group>
131-
<Form.Group className='mb-3'>
132-
<Form.Label>Description</Form.Label>
133-
<Form.Control
134-
as='textarea'
135-
rows={2}
136-
value={createDescription}
137-
onChange={(e) => setCreateDescription(e.target.value)}
138-
required
139-
/>
140-
</Form.Group>
141-
<Button type='submit' variant='primary'>
142-
Create Grant
143-
</Button>
144-
</Form>
145-
146-
<h4 className='mb-3'>Assign Grant to User</h4>
147-
<Form onSubmit={handleAssignGrant} className='mb-4'>
148-
<Form.Group className='mb-3'>
149-
<Form.Label>User Email</Form.Label>
150-
<Form.Control
151-
type='email'
152-
value={assignEmail}
153-
onChange={(e) => setAssignEmail(e.target.value)}
154-
required
155-
/>
156-
</Form.Group>
157-
<Form.Group className='mb-3'>
158-
<Form.Label>Grant</Form.Label>
159-
<Form.Select value={assignGrant} onChange={(e) => setAssignGrant(e.target.value)} required>
160-
{grants.map((grant) => (
161-
<option key={grant.id} value={grant.name}>
162-
{grant.name}
163-
</option>
164-
))}
165-
</Form.Select>
166-
</Form.Group>
167-
<Button type='submit' variant='primary' disabled={grants.length === 0}>
168-
Assign Grant
169-
</Button>
170-
</Form>
171-
172-
<h4 className='mb-3'>Remove Grant from User</h4>
173-
<Form onSubmit={handleRemoveGrant} className='mb-4'>
174-
<Form.Group className='mb-3'>
175-
<Form.Label>User Email</Form.Label>
176-
<Form.Control
177-
type='email'
178-
value={removeEmail}
179-
onChange={(e) => setRemoveEmail(e.target.value)}
180-
required
181-
/>
182-
</Form.Group>
183-
<Form.Group className='mb-3'>
184-
<Form.Label>Grant</Form.Label>
185-
<Form.Select value={removeGrant} onChange={(e) => setRemoveGrant(e.target.value)} required>
186-
{grants.map((grant) => (
187-
<option key={grant.id} value={grant.name}>
188-
{grant.name}
189-
</option>
190-
))}
191-
</Form.Select>
192-
</Form.Group>
193-
<Button type='submit' variant='warning' disabled={grants.length === 0}>
194-
Remove Grant
195-
</Button>
196-
</Form>
59+
<Tabs
60+
activeKey={activeTab}
61+
onSelect={(key) => setActiveTab(key as AdminTab)}
62+
id='admin-grant-tabs'
63+
className='mb-4'
64+
>
65+
<Tab eventKey='grant-users' title='User Grants'>
66+
<UserGrantsTab />
67+
</Tab>
68+
69+
<Tab eventKey='grants' title='Grants'>
70+
<h4 className='mb-3 mt-3'>Grants</h4>
71+
{loading ? (
72+
<Spinner animation='border' />
73+
) : grants.length === 0 ? (
74+
<Alert variant='info'>No grants found.</Alert>
75+
) : (
76+
<Table striped bordered hover responsive className='mb-4'>
77+
<thead>
78+
<tr>
79+
<th>Name</th>
80+
<th>Description</th>
81+
</tr>
82+
</thead>
83+
<tbody>
84+
{grants.map((grant) => (
85+
<tr key={grant.id}>
86+
<td>{grant.name}</td>
87+
<td>{grant.description}</td>
88+
</tr>
89+
))}
90+
</tbody>
91+
</Table>
92+
)}
93+
94+
<h4 className='mb-3'>Create Grant</h4>
95+
<Form onSubmit={handleCreateGrant}>
96+
<Form.Group className='mb-3'>
97+
<Form.Label>Name</Form.Label>
98+
<Form.Control
99+
type='text'
100+
value={createName}
101+
onChange={(e) => setCreateName(e.target.value)}
102+
required
103+
/>
104+
</Form.Group>
105+
<Form.Group className='mb-3'>
106+
<Form.Label>Description</Form.Label>
107+
<Form.Control
108+
as='textarea'
109+
rows={2}
110+
value={createDescription}
111+
onChange={(e) => setCreateDescription(e.target.value)}
112+
required
113+
/>
114+
</Form.Group>
115+
<Button type='submit' variant='primary'>
116+
Create Grant
117+
</Button>
118+
</Form>
119+
</Tab>
120+
</Tabs>
197121

198122
<ToastContainer position='top-center' className='p-3 custom-toast-container' style={{ zIndex: 1100 }}>
199123
{successMessage && (

src/services/GrantService.ts

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import axios, { AxiosError } from 'axios';
22
import { API_ANYWAY_URL } from '../utils/globalEnvs';
3+
import { AdminUserInfo } from '../types/adminUserInfo';
34
import {
45
Grant,
56
CreateGrantPayload,
6-
UserGrantPayload,
7-
DeleteGrantPayload,
7+
SetUserGrantsPayload,
88
} from '../types/grant';
99

1010
const apiUrl = API_ANYWAY_URL;
@@ -21,38 +21,33 @@ class GrantService {
2121
}
2222
}
2323

24-
static async addGrant(data: CreateGrantPayload): Promise<void> {
24+
static async getUserByEmail(email: string): Promise<AdminUserInfo> {
2525
try {
26-
await axios.post(`${apiUrl}/sd-user/add_grant`, data, withCredentials);
27-
} catch (error) {
28-
GrantService.handleError('adding grant', error);
29-
throw error;
30-
}
31-
}
32-
33-
static async addToGrant(data: UserGrantPayload): Promise<void> {
34-
try {
35-
await axios.post(`${apiUrl}/sd-user/add_to_grant`, data, withCredentials);
26+
const response = await axios.get(`${apiUrl}/sd-user/get_user_by_email`, {
27+
...withCredentials,
28+
params: { email },
29+
});
30+
return response.data;
3631
} catch (error) {
37-
GrantService.handleError('assigning grant to user', error);
32+
GrantService.handleError('fetching user by email', error);
3833
throw error;
3934
}
4035
}
4136

42-
static async removeFromGrant(data: UserGrantPayload): Promise<void> {
37+
static async addGrant(data: CreateGrantPayload): Promise<void> {
4338
try {
44-
await axios.post(`${apiUrl}/sd-user/remove_from_grant`, data, withCredentials);
39+
await axios.post(`${apiUrl}/sd-user/add_grant`, data, withCredentials);
4540
} catch (error) {
46-
GrantService.handleError('removing grant from user', error);
41+
GrantService.handleError('adding grant', error);
4742
throw error;
4843
}
4944
}
5045

51-
static async deleteGrant(data: DeleteGrantPayload): Promise<void> {
46+
static async setUserGrants(data: SetUserGrantsPayload): Promise<void> {
5247
try {
53-
await axios.post(`${apiUrl}/sd-user/delete_grant`, data, withCredentials);
48+
await axios.post(`${apiUrl}/sd-user/set_grants`, data, withCredentials);
5449
} catch (error) {
55-
GrantService.handleError('deleting grant', error);
50+
GrantService.handleError('setting user grants', error);
5651
throw error;
5752
}
5853
}

0 commit comments

Comments
 (0)