Skip to content

Commit

Permalink
user invite management
Browse files Browse the repository at this point in the history
  • Loading branch information
larinam committed Aug 19, 2024
1 parent f3ada41 commit 51f67c2
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 55 deletions.
32 changes: 32 additions & 0 deletions backend/routers/users.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import os
import secrets
from datetime import datetime
from typing import Annotated, List

from fastapi import APIRouter, HTTPException, Depends, BackgroundTasks
Expand Down Expand Up @@ -327,3 +328,34 @@ async def register_user_via_invite(token: str, user_creation: UserCreationModel)
invite.mark_as_accepted()

return {"message": "User registered successfully"}


class UserInviteDTO(BaseModel):
id: str = Field(alias="_id", default=None)
email: str
status: str
expiration_date: datetime


@router.get("/invites", response_model=List[UserInviteDTO])
async def list_invites(current_user: Annotated[User, Depends(get_current_active_user_check_tenant)],
tenant: Annotated[Tenant, Depends(get_tenant)]):
invites = UserInvite.objects(tenant=tenant, status__ne="accepted").all()
return [mongo_to_pydantic(invite, UserInviteDTO) for invite in invites]


@router.delete("/invite/{invite_id}")
async def withdraw_invite(invite_id: str,
current_user: Annotated[User, Depends(get_current_active_user_check_tenant)],
tenant: Annotated[Tenant, Depends(get_tenant)]):
invite = UserInvite.objects(id=invite_id, tenant=tenant).first()

if not invite:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Invite not found")

if invite.status == "accepted":
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot withdraw an accepted invite")

invite.delete()

return {"message": "Invite withdrawn successfully"}
72 changes: 72 additions & 0 deletions frontend/src/components/settings/InviteManagement.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, {useEffect, useState} from 'react';
import {useApi} from '../../hooks/useApi';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faTrashAlt} from '@fortawesome/free-solid-svg-icons';

const InviteManagement = () => {
const { apiCall } = useApi();
const [invites, setInvites] = useState([]);

const fetchInvites = async () => {
try {
const response = await apiCall('/users/invites');
setInvites(response);
} catch (error) {
console.error('Error fetching invites:', error);
}
};

const handleWithdrawInvite = async (inviteId, inviteEmail) => {
const isConfirmed = window.confirm(`Are you sure you want to withdraw the invite for: ${inviteEmail}?`);
if (isConfirmed) {
try {
await apiCall(`/users/invite/${inviteId}`, 'DELETE');
fetchInvites(); // Refresh the invites list after withdrawal
} catch (error) {
console.error('Error withdrawing invite:', error);
}
}
};

useEffect(() => {
fetchInvites();
}, []);

if (invites.length === 0) {
return null; // Hide the component if there are no invites
}

return (
<div className="inviteManagementContainer">
<h3>Not accepted Invites</h3>
<table>
<thead>
<tr>
<th>Email</th>
<th>Status</th>
<th>Expiration Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{invites.map((invite, index) => (
<tr key={index}>
<td>{invite.email}</td>
<td>{invite.status}</td>
<td>{new Date(invite.expiration_date).toLocaleDateString()}</td>
<td>
<FontAwesomeIcon
icon={faTrashAlt}
onClick={() => handleWithdrawInvite(invite._id, invite.email)}
/>
</td>
</tr>
))}
</tbody>
</table>
<hr/>
</div>
);
};

export default InviteManagement;
112 changes: 57 additions & 55 deletions frontend/src/components/settings/UserManagement.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {faEdit, faKey, faTrashAlt} from '@fortawesome/free-solid-svg-icons';
import {useAuth} from "../../contexts/AuthContext";
import PasswordChangeModal from "./PasswordChangeModal";
import InviteUserModal from './InviteUserModal';
import InviteManagement from './InviteManagement';

const UserManagement = () => {
const { apiCall } = useApi();
Expand Down Expand Up @@ -60,63 +61,64 @@ const UserManagement = () => {
setShowPasswordModal(true);
};


return (
<div className="settingsUserManagementContainer">
<h2>User Management Settings</h2>
<div className="userManagementButtons">
<button onClick={handleInviteUserClick}>Invite User</button>
</div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Username</th>
<th>Telegram Username</th>
<th>Status</th>
<th>Actions</th>
<div className="settingsUserManagementContainer">
<h2>User Management Settings</h2>
<InviteManagement/>
<h3>Users</h3>
<div className="userManagementButtons">
<button onClick={handleInviteUserClick}>Invite User</button>
</div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Username</th>
<th>Telegram Username</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{users.map((u, index) => (
<tr key={index}>
<td>{u.name}</td>
<td>{u.email}</td>
<td>{u.username}</td>
<td>{u.telegram_username || 'N/A'}</td>
<td>{u.disabled ? 'Disabled' : 'Active'}</td>
<td>
<FontAwesomeIcon icon={faEdit} onClick={() => handleEditUserClick(u)}/>
<FontAwesomeIcon icon={faTrashAlt} onClick={() => handleDeleteUser(u._id, u.name)}/>
{u._id === user._id && (
<FontAwesomeIcon icon={faKey} onClick={() => handlePasswordChangeClick(u)}/>
)}
</td>
</tr>
</thead>
<tbody>
{users.map((u, index) => (
<tr key={index}>
<td>{u.name}</td>
<td>{u.email}</td>
<td>{u.username}</td>
<td>{u.telegram_username || 'N/A'}</td>
<td>{u.disabled ? 'Disabled' : 'Active'}</td>
<td>
<FontAwesomeIcon icon={faEdit} onClick={() => handleEditUserClick(u)} />
<FontAwesomeIcon icon={faTrashAlt} onClick={() => handleDeleteUser(u._id, u.name)} />
{u._id === user._id && (
<FontAwesomeIcon icon={faKey} onClick={() => handlePasswordChangeClick(u)} />
)}
</td>
</tr>
))}
</tbody>
</table>
{showUserModal && (
<UserModal
isOpen={showUserModal}
onClose={handleModalClose}
editingUser={editingUser} // Pass editing user data to the modal
/>
)}
{showInviteModal && (
<InviteUserModal
isOpen={showInviteModal}
onClose={handleModalClose}
/>
)}
{showPasswordModal && (
<PasswordChangeModal
isOpen={showPasswordModal}
onClose={() => setShowPasswordModal(false)}
/>
)}
</div>
))}
</tbody>
</table>
{showUserModal && (
<UserModal
isOpen={showUserModal}
onClose={handleModalClose}
editingUser={editingUser} // Pass editing user data to the modal
/>
)}
{showInviteModal && (
<InviteUserModal
isOpen={showInviteModal}
onClose={handleModalClose}
/>
)}
{showPasswordModal && (
<PasswordChangeModal
isOpen={showPasswordModal}
onClose={() => setShowPasswordModal(false)}
/>
)}
</div>
);
};

Expand Down

0 comments on commit 51f67c2

Please sign in to comment.