Skip to content

Commit

Permalink
Merge pull request #326 from ReseauEntourage/feature/EN-6995-Signaler…
Browse files Browse the repository at this point in the history
…-un-utilisateur

[EN-6995] Signaler un utilisateur
  • Loading branch information
guillobits authored Sep 19, 2024
2 parents cc796bf + 5a32606 commit 005b588
Show file tree
Hide file tree
Showing 22 changed files with 409 additions and 4 deletions.
8 changes: 8 additions & 0 deletions src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
UserDto,
UserProfile,
UserRegistrationDto,
UserReportDto,
UserWithUserCandidate,
} from './types';

Expand Down Expand Up @@ -294,6 +295,13 @@ export class APIHandler {
return this.put(`/user/profile/${userId}`, userProfile);
}

postProfileUserAbuse(
userId: string,
userReportDto: UserReportDto
): Promise<AxiosResponse> {
return this.post(`/user/profile/${userId}/report`, userReportDto);
}

// delete

deleteUser(userId: string): Promise<AxiosResponse> {
Expand Down
5 changes: 5 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ export type UserProfile = {
hasExternalCv: boolean;
};

export type UserReportDto = {
reason: string;
comment: string;
};

export interface WhatsappJoinUrl {
name: string;
qrCodePath: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { useCallback, useMemo } from 'react';
import { useOnReportUserFormSubmit } from '../useOnReportUserFormSubmit';
import { Api } from 'src/api';
import { UserReportDto } from 'src/api/types';
import { formReportUser } from 'src/components/forms/schemas/formReportUser';
import { ModalEdit } from 'src/components/modals/Modal/ModalGeneric/ModalEdit';
import { Actions } from 'src/constants/utils';

interface ProfileReportUserModalProps {
userId: string;
}

export const ProfileReportUserModal = ({
userId,
}: ProfileReportUserModalProps) => {
const { onSubmit } = useOnReportUserFormSubmit(
async (userReportDto: UserReportDto) => {
return Api.postProfileUserAbuse(userId, userReportDto);
},
Actions.CREATE
);

const handleReportUserSubmit = useCallback(
async (fields, closeModal) => {
await onSubmit(fields, closeModal);
},
[onSubmit]
);

const updateUserModalProps = useMemo(() => {
return {
formId: 'id',
formSchema: formReportUser,
title: 'Signaler un utilisateur',
description:
'Vous pouvez signaler un utilisateur si vous pensez qu’il ne respecte pas les règles de la plateforme.',
submitText: 'Envoyer',
cancelText: 'Annuler',
onSubmit: handleReportUserSubmit,
defaultValues: {
reason: '',
comment: '',
},
};
}, [handleReportUserSubmit]);

return <ModalEdit {...updateUserModalProps} />;
};
46 changes: 46 additions & 0 deletions src/components/backoffice/profile/useOnReportUserFormSubmit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { AxiosResponse } from 'axios';
import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { UserReportDto } from 'src/api/types';
import { ExtractFormSchemaValidation } from 'src/components/forms/FormSchema';
import { formReportUser } from 'src/components/forms/schemas/formReportUser';
import { Action, ActionsLabels } from 'src/constants/utils';
import { notificationsActions } from 'src/use-cases/notifications';

export function useOnReportUserFormSubmit(
apiCall: (organization: UserReportDto) => Promise<AxiosResponse>,
action: Action
) {
const dispatch = useDispatch();
const onSubmit = useCallback(
async (
fields: ExtractFormSchemaValidation<typeof formReportUser>,
closeModal
) => {
try {
const { data } = await apiCall(fields);
closeModal();
dispatch(
notificationsActions.addNotification({
type: 'success',
message: `Le signalement a bien été ${ActionsLabels[action].VERB}e`,
})
);
return data;
} catch (error) {
console.error(error);
dispatch(
notificationsActions.addNotification({
type: 'danger',
message: `Une erreur s'est produite lors de la ${ActionsLabels[action].NAME} du signalement`,
})
);
}
},
[action, apiCall, dispatch]
);

return {
onSubmit,
};
}
26 changes: 26 additions & 0 deletions src/components/forms/schemas/formReportUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { FormSchema } from '../FormSchema';
import { USER_REPORT_REASONS } from 'src/constants/users';

export const formReportUser: FormSchema<{
reason: string;
comment: string;
}> = {
id: 'form-add-organization',
fields: [
{
id: 'reason',
name: 'reason',
component: 'select-simple',
title: 'Raison du signalement *',
options: USER_REPORT_REASONS,
isRequired: true,
},
{
id: 'comment',
name: 'comment',
component: 'text-input',
title: 'Commentaire *',
isRequired: true,
},
],
};
11 changes: 8 additions & 3 deletions src/components/headers/HeaderProfile/HeaderProfile.desktop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import { AvailabilityTag } from 'src/components/utils/AvailabilityTag/Availabili
import { H1, H5 } from 'src/components/utils/Headings';
import { ImageInput } from 'src/components/utils/Inputs';
import { Spinner } from 'src/components/utils/Spinner';
import { UserActions } from 'src/components/utils/UserActions/UserActions';
import { COLORS } from 'src/constants/styles';
import { USER_ROLES } from 'src/constants/users';
import {
StyledHeaderAvailibilityAndUserActions,
StyledHeaderNameAndRole,
StyledHeaderProfile,
StyledHeaderProfileContent,
Expand Down Expand Up @@ -158,9 +160,12 @@ export const HeaderProfileDesktop = ({
style="secondary"
/>
</StyledHeaderNameAndRole>
{shouldShowAllProfile && (
<AvailabilityTag isAvailable={isAvailable} />
)}
<StyledHeaderAvailibilityAndUserActions>
{shouldShowAllProfile && (
<AvailabilityTag isAvailable={isAvailable} />
)}
<UserActions userId={id} />
</StyledHeaderAvailibilityAndUserActions>
</StyledHeaderProfileNameContainer>
{shouldShowAllProfile && (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import { AvailabilityTag } from 'src/components/utils/AvailabilityTag/Availabili
import { H2, H6 } from 'src/components/utils/Headings';
import { ImageInput } from 'src/components/utils/Inputs';
import { Spinner } from 'src/components/utils/Spinner';
import { UserActions } from 'src/components/utils/UserActions/UserActions';
import { COLORS } from 'src/constants/styles';
import { USER_ROLES } from 'src/constants/users';
import {
StyledEditPictureIconContainer,
StyledHeaderAvailibilityAndUserActions,
StyledHeaderNameAndRoleMobile,
StyledHeaderProfile,
StyledHeaderProfileContent,
Expand Down Expand Up @@ -134,7 +136,10 @@ export const HeaderProfileMobile = ({
</StyledHeaderProfileContent>
{shouldShowAllProfile && (
<StyledHeaderProfileDescription>
<AvailabilityTag isAvailable={isAvailable} />
<StyledHeaderAvailibilityAndUserActions>
<AvailabilityTag isAvailable={isAvailable} />
<UserActions userId={id} openDirection="right" />
</StyledHeaderAvailibilityAndUserActions>
<ProfileDescription
description={description}
isEditable={isEditable}
Expand Down
5 changes: 5 additions & 0 deletions src/components/headers/HeaderProfile/HeaderProfile.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ export const StyledHeaderNameAndRole = styled.div`
}
`;

export const StyledHeaderAvailibilityAndUserActions = styled.div`
display: flex;
gap: 10px;
`;

export const StyledHeaderNameAndRoleMobile = styled(StyledHeaderNameAndRole)`
margin-bottom: 10px;
`;
Expand Down
4 changes: 4 additions & 0 deletions src/components/utils/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ interface DropdownProps {
boundaryId?: string;
}

/**
* Dropdown component (using UIKit)
* @deprecated This component is deprecated. Please use the Dropdown component in the ./Dropdown folder
*/
export const Dropdown = ({
id = 'dropdown',
children,
Expand Down
7 changes: 7 additions & 0 deletions src/components/utils/Dropdown/Dropdown.context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

export const DropdownContext = React.createContext({
isOpen: false,
toggleDropdown: () => {},
closeDropdown: () => {},
});
41 changes: 41 additions & 0 deletions src/components/utils/Dropdown/Dropdown.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import styled from 'styled-components';
import { COLORS } from 'src/constants/styles';

export const StyledDropdown = styled.div`
position: relative;
display: inline-block;
`;

export const StyledDropdownMenu = styled.div`
display: flex;
flex-direction: column;
gap: 15px;
position: absolute;
top: calc(100% + 10px);
background-color: white;
border: 1px solid #ececec;
border-radius: 20px;
box-shadow: 0 4px 4px 0px #0000000d;
padding: 25px;
z-index: 1000;
min-width: 200px;
${({ openDirection }) => {
return openDirection === 'right' ? 'left: 0;' : '';
}}
${({ openDirection }) => {
return openDirection === 'left' ? 'right: 0;' : '';
}}
`;

export const StyledDropdownMenuItem = styled.div`
color: #484848;
font-size: 16px;
cursor: pointer;
:hover {
color: ${COLORS.primaryBlue};
}
`;

export const StyledDropdownMenuItemSeparator = styled.div`
border-top: 1px solid #ececec;
`;
47 changes: 47 additions & 0 deletions src/components/utils/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { useState, useRef, useEffect } from 'react';
import { DropdownContext } from './Dropdown.context';
import { StyledDropdown } from './Dropdown.styles';
import { DropdownItem } from './DropdownItem';
import { DropdownItemSeparator } from './DropdownItemSeparator';
import { DropdownMenu } from './DropdownMenu';
import { DropdownToggle } from './DropdownToggle';

type DropdownProps = {
children: React.ReactNode;
};

const Dropdown = ({ children }: DropdownProps) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);

const toggleDropdown = () => setIsOpen((prev) => !prev);
const closeDropdown = () => setIsOpen(false);

const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
closeDropdown();
}
};

useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<DropdownContext.Provider value={{ isOpen, toggleDropdown, closeDropdown }}>
<StyledDropdown ref={dropdownRef}>{children}</StyledDropdown>
</DropdownContext.Provider>
);
};

Dropdown.Toggle = DropdownToggle;
Dropdown.Menu = DropdownMenu;
Dropdown.ItemSeparator = DropdownItemSeparator;
Dropdown.Item = DropdownItem;

export { Dropdown };
23 changes: 23 additions & 0 deletions src/components/utils/Dropdown/DropdownItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { DropdownContext } from './Dropdown.context';
import { StyledDropdownMenuItem } from './Dropdown.styles';

type DropdownItemProps = {
children: React.ReactNode;
onClick?: () => void;
};

export const DropdownItem = ({ children, onClick }: DropdownItemProps) => {
const { closeDropdown } = React.useContext(DropdownContext);

const handleClick = () => {
if (onClick) onClick();
closeDropdown();
};

return (
<StyledDropdownMenuItem onClick={handleClick} className="dropdown-item">
{children}
</StyledDropdownMenuItem>
);
};
6 changes: 6 additions & 0 deletions src/components/utils/Dropdown/DropdownItemSeparator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import { StyledDropdownMenuItemSeparator } from './Dropdown.styles';

export const DropdownItemSeparator = () => {
return <StyledDropdownMenuItemSeparator />;
};
25 changes: 25 additions & 0 deletions src/components/utils/Dropdown/DropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { DropdownContext } from './Dropdown.context';
import { StyledDropdownMenu } from './Dropdown.styles';

type DropdownMenuProps = {
children: React.ReactNode;
openDirection?: 'left' | 'right';
};

export const DropdownMenu = ({
children,
openDirection = 'right',
}: DropdownMenuProps) => {
const { isOpen } = React.useContext(DropdownContext);

return (
<>
{isOpen && (
<StyledDropdownMenu openDirection={openDirection}>
{children}
</StyledDropdownMenu>
)}
</>
);
};
Loading

0 comments on commit 005b588

Please sign in to comment.