Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions src/components/Identity/CreateUserDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { Button, CloseButton, Dialog, Field, IconButton, Input, Portal, Text, VStack } from '@chakra-ui/react';
import { useState, useEffect } from 'react';
import { FaPlus } from 'react-icons/fa';
import { useNavigate } from 'react-router-dom';
import { withMask } from 'use-mask-input';
import ToastWizard from '../toastWizard';
import server, { JSONResponse } from '../../networking';

function CreateUserDialog() {
const navigate = useNavigate();

const [dialogOpen, setDialogOpen] = useState(false);
const [fname, setFname] = useState('');
const [lname, setLname] = useState('');
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [contact, setContact] = useState('');
const [role, setRole] = useState('');
const [creatingUser, setCreatingUser] = useState(false);

const isDisabled = !fname || !lname || !username || !email || !contact || !role;

const handleCreateUser = async () => {
if (isDisabled) {
ToastWizard.standard("error", "All fields are required.");
return;
}

setCreatingUser(true);

try {
const response = await server.post('/admin/createUser', {
fname,
lname,
username,
email,
contact,
role
})

if (response.data instanceof JSONResponse) {
if (response.data.isErrorStatus()) {
const errObject = {
response: {
data: response.data
}
};
throw new Error(errObject);
}

// Success case
ToastWizard.standard("success", "Account Created", `Welcome ${fname} to the family! Login instructions have been sent via email!`);
setDialogOpen(false);
if (response.data.raw.newUserID) {
navigate(`/profile/${response.data.raw.newUserID}`);
}
} else {
throw new Error("Unexpected response format");
}
} catch (err) {
if (err.response && err.response.data instanceof JSONResponse) {
console.log("Error response in user creation request:", err.response.data.fullMessage());
if (err.response.data.userErrorType()) {
ToastWizard.standard("error", "User creation failed.", err.response.data.message);
} else {
ToastWizard.standard("error", "Something went wrong", "Couldn't create user. Please try again.")
}
} else {
console.log("Unexpected error in user creation request:", err);
ToastWizard.standard("error", "Something went wrong", "Couldn't create user. Please try again.")
}
} finally {
setCreatingUser(false);
}
}

const handleEnterKey = (e) => {
if (e.key === 'Enter') {
handleCreateUser();
}
}

return (
<Dialog.Root placement={'center'} size={'lg'} closeOnInteractOutside={false} open={dialogOpen} onOpenChange={(e) => setDialogOpen(e.open)} unmountOnExit>
<Dialog.Trigger asChild>
<IconButton bgColor={'primaryColour'} ml={'20px'}><FaPlus /></IconButton>
</Dialog.Trigger>
<Portal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title><Text fontWeight={'bold'}>Create New Account</Text></Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<VStack display={'flex'} justifyContent={'center'} gap={'10px'}>
<Text>Create new accounts to enable more people to gain access to ArchAIve's innovative artefact digitisation services!</Text>
<Text>Users will be able to access most of the platform's features, except those that are sensitive like managing other users.</Text>
<Text>Every user's activity is monitored closely, empowering you to redress in any dire situations at all times.</Text>
</VStack>
<VStack mt={'20px'} gap={'10px'} w={'100%'}>
<Field.Root mt={'10px'} w={'100%'}>
<Field.Label fontSize={'md'}>First Name</Field.Label>
<Input placeholder="e.g John" type='text' onKeyDown={handleEnterKey} value={fname} onChange={e => setFname(e.target.value)} />
</Field.Root>

<Field.Root mt={'10px'} w={'100%'}>
<Field.Label fontSize={'md'}>Last Name</Field.Label>
<Input placeholder="e.g Appleseed" type='text' onKeyDown={handleEnterKey} value={lname} onChange={e => setLname(e.target.value)} />
</Field.Root>

<Field.Root mt={'10px'} w={'100%'}>
<Field.Label fontSize={'md'}>Username</Field.Label>
<Input placeholder="e.g johnappleseed" type='text' onKeyDown={handleEnterKey} value={username} onChange={e => setUsername(e.target.value)} />
</Field.Root>

<Field.Root mt={'10px'} w={'100%'}>
<Field.Label fontSize={'md'}>Email</Field.Label>
<Input placeholder="e.g me@example.com" type='email' onKeyDown={handleEnterKey} value={email} onChange={e => setEmail(e.target.value)} />
</Field.Root>

<Field.Root mt={'10px'} w={'100%'}>
<Field.Label fontSize={'md'}>Contact</Field.Label>
<Input placeholder="e.g 1234 5678" type='tel' onKeyDown={handleEnterKey} ref={withMask("9999 9999")} value={contact} onChange={e => setContact(e.target.value)} />
</Field.Root>

<Field.Root mt={'10px'} w={'100%'}>
<Field.Label fontSize={'md'}>Role</Field.Label>
<Input placeholder="e.g Researcher" type='text' onKeyDown={handleEnterKey} value={role} onChange={e => setRole(e.target.value)} />
</Field.Root>
</VStack>
</Dialog.Body>
<Dialog.Footer>
<Dialog.ActionTrigger asChild>
<Button variant="outline">Cancel</Button>
</Dialog.ActionTrigger>
<Button variant={'ArchPrimary'} onClick={handleCreateUser} disabled={isDisabled || creatingUser} loading={creatingUser} loadingText={'Creating...'}>Create User</Button>
</Dialog.Footer>
<Dialog.CloseTrigger asChild>
<CloseButton size="sm" />
</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</Dialog.Root>
)
}

export default CreateUserDialog
14 changes: 1 addition & 13 deletions src/pages/AdminConsole.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function AdminConsole() {
return (
<Box p={'20px'}>
<Text fontSize={'2xl'} fontWeight={'bold'}>Admin Console</Text>
<Box display={'flex'} flexDir={'row'} justifyContent={'space-around'} alignItems={'center'} h={'100%'} w={'100%'} mt={'20px'}>
<Box display={'flex'} flexDir={'row'} justifyContent={'flex-start'} flexWrap={'wrap'} gap={'20px'} alignItems={'center'} h={'100%'} w={'100%'} mt={'20px'}>
<Card.Root width="320px" variant={'elevated'} size={'lg'}>
<Card.Body gap="2">
<Card.Title mb="2">User Management</Card.Title>
Expand All @@ -25,18 +25,6 @@ function AdminConsole() {
</Card.Footer>
</Card.Root>

<Card.Root width="320px" variant={'elevated'} size={'lg'}>
<Card.Body gap="2">
<Card.Title mb="2">Data Management</Card.Title>
<Card.Description fontSize={'md'}>
Analyse and manage batches, collections, artefacts and other data that goes into the platform.
</Card.Description>
</Card.Body>
<Card.Footer justifyContent="flex-end">
<Button variant={'ArchPrimary'} size={'md'}>Manage Data</Button>
</Card.Footer>
</Card.Root>

<Card.Root width="320px" variant={'elevated'} size={'lg'}>
<Card.Body gap="2">
<Card.Title mb="2">Platform Controls</Card.Title>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Profile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ function Profile() {
<Text alignSelf={'flex-start'} fontSize={'xl'} fontWeight={'bolder'} mt={'20px'}>Security & Authentication</Text>

<SkeletonText noOfLines={1} loading={infoLoading} textAlign={'left'}>
<Text>Last Login: {originalAccountInfo.lastLogin}</Text>
<Text>Last Login: {originalAccountInfo.lastLogin || 'Never'}</Text>
</SkeletonText>

<Spacer />
Expand Down
8 changes: 3 additions & 5 deletions src/pages/UserManagement.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { Box, Button, IconButton, SimpleGrid, Skeleton, Spinner, Text } from '@chakra-ui/react'
import { FaPlus } from 'react-icons/fa'
import { Box, SimpleGrid, Skeleton, Text } from '@chakra-ui/react'
import IAMStatisticBox from '../components/Identity/IAMStatisticBox'
import IAMUserCard from '../components/Identity/IAMUserCard'
import { useNavigate } from 'react-router-dom'
import { useSelector } from 'react-redux'
import { useState } from 'react'
import server, { JSONResponse } from '../networking';
import ToastWizard from '../components/toastWizard';
import { useEffect } from 'react'
import CreateUserDialog from '../components/Identity/CreateUserDialog'

function UserManagement() {
const navigate = useNavigate();
const { loaded, superuser } = useSelector(state => state.auth);

const [userData, setUserData] = useState({});
Expand Down Expand Up @@ -68,7 +66,7 @@ function UserManagement() {
<Box display={'flex'} flexDir={'column'} alignItems={'flex-start'} justifyContent={'center'} h={'100%'} w={'100%'} p={'20px'} mt={'20px'}>
<Box display={'flex'} flexDir={'row'} alignItems={'flex-start'}>
<Text fontSize={'2xl'} fontWeight={'bold'}>User Management</Text>
<IconButton bgColor={'primaryColour'} ml={'20px'}><FaPlus /></IconButton>
<CreateUserDialog />
</Box>

<Box display={'flex'} flexDir={'row'} alignItems={'center'} justifyContent={'space-around'} w={'100%'} mt={'20px'} flexWrap={'wrap'} gap={'20px'}>
Expand Down