Skip to content
Draft
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
5 changes: 3 additions & 2 deletions backend/typescript/middlewares/validators/userValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
if (!validatePrimitive(req.body.role, "string")) {
return res.status(400).send(getApiValidationError("role", "string"));
}
if (!validatePrimitive(req.body.colorLevel, "integer")) {
// put a false here, not sure what a color level is because the backend literally sets it to 1 on creation anyway
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix this

// TODO: remove this check altogether
if (false && !validatePrimitive(req.body.colorLevel, "integer")) {

Check warning on line 31 in backend/typescript/middlewares/validators/userValidators.ts

View workflow job for this annotation

GitHub Actions / run-lint

Unexpected constant condition
return res.status(400).send(getApiValidationError("colorLevel", "integer"));
}
if (
Expand Down Expand Up @@ -54,7 +56,6 @@
) {
return res.status(400).send(getApiValidationError("phoneNumber", "string"));
}

return next();
};

Expand Down
6 changes: 4 additions & 2 deletions backend/typescript/rest/authRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,15 @@ authRouter.post("/invite-user", inviteUserDtoValidator, async (req, res) => {
const invitedUser = user;
invitedUser.status = UserStatus.INVITED;
await userService.updateUserById(user.id, invitedUser);

res.status(204).send();
} catch (error: unknown) {
if (error instanceof NotFoundError) {
res.status(404).send(getErrorMessage(error));
} else {
res.status(500).json({ error: getErrorMessage(error) });
await userService.deleteUserByEmail(req.body.email);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behaviour wasn't part of the ticket requirements, can you explain this decision?

Note: invite user endpoint does NOT create a user.

res
.status(500)
.json({ error: `${getErrorMessage(error)}\nUser has been deleted!` });
}
}
});
Expand Down
1 change: 0 additions & 1 deletion backend/typescript/services/implementations/userService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ class UserService implements IUserService {
async createUser(user: CreateUserDTO): Promise<UserDTO> {
let newUser: PgUser;
let firebaseUser: firebaseAdmin.auth.UserRecord;

try {
firebaseUser = await firebaseAdmin.auth().createUser({
email: user.email,
Expand Down
22 changes: 20 additions & 2 deletions frontend/src/APIClients/UserAPIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async function get(userId?: number): Promise<User | User[]> {
}
}

const create = async (formData: CreateUserDTO): Promise<CreateUserDTO> => {
const create = async (formData: CreateUserDTO): Promise<User> => {
const bearerToken = `Bearer ${getLocalStorageObjProperty(
AUTHENTICATED_USER_KEY,
"accessToken",
Expand All @@ -43,6 +43,24 @@ const create = async (formData: CreateUserDTO): Promise<CreateUserDTO> => {
}
};

const update = async (
userId: number,
formData: Partial<User>,
): Promise<User> => {
const bearerToken = `Bearer ${getLocalStorageObjProperty(
AUTHENTICATED_USER_KEY,
"accessToken",
)}`;
try {
const { data } = await baseAPIClient.put(`/users/${userId}`, formData, {
headers: { Authorization: bearerToken },
});
return data;
} catch (error) {
throw new Error(`Failed to update user: ${error}`);
}
};

const invite = async (email: string): Promise<void> => {
const bearerToken = `Bearer ${getLocalStorageObjProperty(
AUTHENTICATED_USER_KEY,
Expand All @@ -61,4 +79,4 @@ const invite = async (email: string): Promise<void> => {
}
};

export default { get, create, invite };
export default { get, create, update, invite };
7 changes: 7 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import InteractionLogPage from "./features/interaction-log/pages/InteractionLogP
import ProfilePage from "./features/user-profile/pages/UserProfilePage";
import PetProfilePage from "./features/pet-profile/pages/PetProfilePage";
import UserManagementPage from "./features/user-management/pages/UserManagementPage";
import InviteUserPage from "./features/user-management/pages/InviteUserPage";
import AdminViewEditUserProfilePage from "./features/user-profile/pages/AdminViewEditUserProfilePage";
import AdminPage from "./pages/AdminPage";
import Layout from "./Layout";
Expand Down Expand Up @@ -106,6 +107,12 @@ const App = (): React.ReactElement => {
component={UserManagementPage}
allowedRoles={AuthConstants.STAFF_BEHAVIOURISTS_ADMIN}
/>
<PrivateRoute
exact
path={Routes.INVITE_USER_PAGE}
component={InviteUserPage}
allowedRoles={AuthConstants.ADMIN_AND_BEHAVIOURISTS}
/>
<PrivateRoute
exact
path={`${Routes.ADMIN_EDIT_USER_PROFILE_PAGE}/:userId`}
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/components/common/MultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface MultiSelectProps<T> {
label?: string;
error?: boolean;
colours: string[];
icons?: React.FC<React.SVGProps<SVGSVGElement>>[];
required?: boolean;
maxHeight?: string;
}
Expand All @@ -32,6 +33,7 @@ const MultiSelect = <T extends string | number>({
label,
error = false,
colours,
icons,
required = false,
maxHeight = "200px",
}: MultiSelectProps<T>): React.ReactElement => {
Expand Down Expand Up @@ -159,6 +161,7 @@ const MultiSelect = <T extends string | number>({
>
{selected.map((value) => {
const color = getColorForValue(value);
const IconComponent = icons?.[values.indexOf(value)];

return (
<Tag
Expand All @@ -168,7 +171,12 @@ const MultiSelect = <T extends string | number>({
flexShrink={0}
px="1.5rem"
py="0.25rem"
display="flex"
gap="0.25rem"
>
{IconComponent && (
<Icon as={IconComponent} boxSize="1rem" />
)}
<TagLabel textStyle="button" m={0}>
{String(value)}
</TagLabel>
Expand Down Expand Up @@ -248,6 +256,7 @@ const MultiSelect = <T extends string | number>({
{values.map((value, index) => {
const isSelected = selected.includes(value);
const isLastItem = index === values.length - 1;
const IconComponent = icons?.[index];

return (
<Box key={String(value)}>
Expand Down Expand Up @@ -278,6 +287,9 @@ const MultiSelect = <T extends string | number>({
isChecked={isSelected}
onChange={() => handleSelect(value)}
/>
{IconComponent && (
<Icon as={IconComponent} boxSize="1.5rem" />
)}
<Text
m={0}
textStyle="body"
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/constants/Routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const PET_PROFILE_PAGE = "/pet-profile";

export const USER_MANAGEMENT_PAGE = "/admin/users";

export const INVITE_USER_PAGE = "/admin/users/invite";

export const ADMIN_EDIT_USER_PROFILE_PAGE = "/admin/edit-user-profile";

export const TASK_MANAGEMENT_PAGE = "/admin/tasks";
Expand Down
102 changes: 0 additions & 102 deletions frontend/src/features/user-management/components/AddUserFormModal.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const UserListTableSection = ({
type={imageType}
/>
<Text textStyle="body" m={0} color={textColor}>
{user.name}
{user.firstName} {user.lastName}
</Text>
</Flex>
</Td>
Expand Down
Loading
Loading