diff --git a/package-lock.json b/package-lock.json
index 30f652c3..093bead3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -33,6 +33,7 @@
"formik-mui": "^4.0.0-alpha.3",
"git-url-parse": "^14.0.0",
"history": "^5.3.0",
+ "jdenticon": "^3.3.0",
"js-cookie": "^3.0.1",
"katex": "^0.16.4",
"lodash": "^4.17.21",
@@ -43,6 +44,7 @@
"react-diff-viewer-continued": "^3.4.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^2.0.4",
+ "react-jdenticon": "^1.4.0",
"react-markdown": "^8.0.7",
"react-markdown-editor-lite": "^1.3.4",
"react-redux": "^8.0.5",
@@ -7655,6 +7657,14 @@
}
]
},
+ "node_modules/canvas-renderer": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/canvas-renderer/-/canvas-renderer-2.2.1.tgz",
+ "integrity": "sha512-RrBgVL5qCEDIXpJ6NrzyRNoTnXxYarqm/cS/W6ERhUJts5UQtt/XPEosGN3rqUkZ4fjBArlnCbsISJ+KCFnIAg==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/cbw-sdk": {
"name": "@coinbase/wallet-sdk",
"version": "3.9.3",
@@ -10778,6 +10788,20 @@
"ws": "*"
}
},
+ "node_modules/jdenticon": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/jdenticon/-/jdenticon-3.3.0.tgz",
+ "integrity": "sha512-DhuBRNRIybGPeAjMjdHbkIfiwZCCmf8ggu7C49jhp6aJ7DYsZfudnvnTY5/1vgUhrGA7JaDAx1WevnpjCPvaGg==",
+ "dependencies": {
+ "canvas-renderer": "~2.2.0"
+ },
+ "bin": {
+ "jdenticon": "bin/jdenticon.js"
+ },
+ "engines": {
+ "node": ">=6.4.0"
+ }
+ },
"node_modules/jest-environment-node": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
@@ -14670,6 +14694,18 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
},
+ "node_modules/react-jdenticon": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/react-jdenticon/-/react-jdenticon-1.4.0.tgz",
+ "integrity": "sha512-yq9laq2ccH0MeSOZ7aTD21TNl0MsmADgDqVV5JIIMIaQOY7Ybpstbv/4T3ArSRatr+fuo2Xh2HUhwJG/UoELKQ==",
+ "dependencies": {
+ "jdenticon": "^3.1.1",
+ "prop-types": "^15.8.1"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
"node_modules/react-markdown": {
"version": "8.0.7",
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz",
diff --git a/package.json b/package.json
index 716eedb4..95a7f09d 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"formik-mui": "^4.0.0-alpha.3",
"git-url-parse": "^14.0.0",
"history": "^5.3.0",
+ "jdenticon": "^3.3.0",
"js-cookie": "^3.0.1",
"katex": "^0.16.4",
"lodash": "^4.17.21",
@@ -45,6 +46,7 @@
"react-diff-viewer-continued": "^3.4.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^2.0.4",
+ "react-jdenticon": "^1.4.0",
"react-markdown": "^8.0.7",
"react-markdown-editor-lite": "^1.3.4",
"react-redux": "^8.0.5",
diff --git a/src/components/AddUserToOrganization/AddUserToOrganization.jsx b/src/components/AddUserToOrganization/AddUserToOrganization.jsx
new file mode 100644
index 00000000..20fb2286
--- /dev/null
+++ b/src/components/AddUserToOrganization/AddUserToOrganization.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import Box from '@mui/material/Box';
+
+const AddUserToOrganization = () => {
+ return ;
+};
+//
+export default AddUserToOrganization;
diff --git a/src/components/Audit-card.jsx b/src/components/Audit-card.jsx
index 4b3b505e..1efca5d9 100644
--- a/src/components/Audit-card.jsx
+++ b/src/components/Audit-card.jsx
@@ -22,23 +22,58 @@ const AuditCard = ({ audit, request }) => {
{audit.project_name}
-
-
+
+
+ {audit?.auditor_organization?.contacts?.email !== null
+ ? audit?.auditor_organization?.contacts?.email
+ : 'Hidden'}
+
+
+
+ ) : (
+
+
+
+ {audit?.auditor_contacts.email !== null
+ ? audit?.auditor_contacts?.email
+ : 'Hidden'}
+
+
+
+ )}
+ {audit?.auditor_organization?.id && (
+
-
- {audit?.auditor_contacts.email !== null
- ? audit?.auditor_contacts?.email
- : 'Hidden'}
+ Organization
+
+ {audit?.auditor_organization?.name}
-
-
+
+ )}
el).join(', ') ?? ''}
arrow
diff --git a/src/components/Audit-request-card.jsx b/src/components/Audit-request-card.jsx
index e8767033..f46203a8 100644
--- a/src/components/Audit-request-card.jsx
+++ b/src/components/Audit-request-card.jsx
@@ -48,6 +48,24 @@ const AuditRequestCard = ({ type, request, audit }) => {
+ {request.auditor_organization?.id && (
+
+
+ Organization:
+
+
+ {request.auditor_organization.name}
+
+
+ )}
);
diff --git a/src/components/AuditorModal.jsx b/src/components/AuditorModal.jsx
index fb4ab902..a0e35f9c 100644
--- a/src/components/AuditorModal.jsx
+++ b/src/components/AuditorModal.jsx
@@ -31,6 +31,7 @@ import { setCurrentChat } from '../redux/actions/chatActions.js';
import ChatIcon from './icons/ChatIcon.jsx';
import { getAuditorRating } from '../redux/actions/auditorAction.js';
import Star from './icons/Star.jsx';
+import TypeChat from './Chat/TypeChat.jsx';
import { SCOPE_GIT_BLOCK, SCOPE_LINKS } from '../services/constants.js';
export default function AuditorModal({
@@ -52,10 +53,12 @@ export default function AuditorModal({
const { auditorRating } = useSelector(s => s.auditor);
const { chatList } = useSelector(s => s.chat);
const myProjects = useSelector(state => state.project.myProjects);
+ const [isOpenType, setIsOpenType] = useState(false);
const [mode, setMode] = useState('info');
const [message, setMessage] = useState('');
const [scope, setScope] = useState([]);
+ const { organizations, own } = useSelector(s => s.organization);
const handleInvite = () => {
if (
@@ -94,29 +97,34 @@ export default function AuditorModal({
};
const handleSendMessage = () => {
- window.scrollTo(0, 0);
+ // TODO add check for pm or org chat
+ if (organizations.length || own.length) {
+ setIsOpenType(true);
+ } else {
+ window.scrollTo(0, 0);
- const existingChat = chatList.find(chat =>
- chat.members?.find(
- member =>
- member.id === auditor?.user_id &&
- member.role?.toLowerCase() === AUDITOR,
- ),
- );
- const chatId = existingChat ? existingChat.id : auditor?.user_id;
- const members = [auditor?.user_id, user.id];
+ const existingChat = chatList.find(chat =>
+ chat.members?.find(
+ member =>
+ member.id === auditor?.user_id &&
+ member.role?.toLowerCase() === AUDITOR,
+ ),
+ );
+ const chatId = existingChat ? existingChat.id : auditor?.user_id;
+ const members = [auditor?.user_id, user.id];
- dispatch(
- setCurrentChat(chatId, {
- name: auditor.first_name,
- avatar: auditor.avatar,
- role: AUDITOR,
- isNew: !existingChat,
- members,
- }),
- );
- localStorage.setItem('path', window.location.pathname);
- navigate(`/chat/${existingChat ? existingChat.id : auditor?.user_id}`);
+ dispatch(
+ setCurrentChat(chatId, {
+ name: auditor.first_name,
+ avatar: auditor.avatar,
+ role: AUDITOR,
+ isNew: !existingChat,
+ members,
+ }),
+ );
+ localStorage.setItem('path', window.location.pathname);
+ navigate(`/chat/${existingChat ? existingChat.id : auditor?.user_id}`);
+ }
};
useEffect(() => {
@@ -306,14 +314,17 @@ export default function AuditorModal({
Invite to project
{!budge && (
-
+ //
+
)}
@@ -678,6 +689,7 @@ const dateWrapper = {
},
},
};
+
const dateStyle = {
width: '150px',
height: '40px',
diff --git a/src/components/AuditorSearchModal.jsx b/src/components/AuditorSearchModal.jsx
index 380727d7..aba4521e 100644
--- a/src/components/AuditorSearchModal.jsx
+++ b/src/components/AuditorSearchModal.jsx
@@ -8,7 +8,15 @@ import { Box } from '@mui/system';
import InputAdornment from '@mui/material/InputAdornment';
import SearchIcon from '@mui/icons-material/Search';
import { useEffect, useRef, useState, useCallback } from 'react';
-import { Paper, Typography } from '@mui/material';
+import Autocomplete from '@mui/material/Autocomplete';
+import {
+ Avatar,
+ Checkbox,
+ FormControlLabel,
+ Paper,
+ Slider,
+ Typography,
+} from '@mui/material';
import AuditorSearchListBox from './custom/AuditorSearchListBox.jsx';
import IconButton from '@mui/material/IconButton';
import { ArrowBack } from '@mui/icons-material';
@@ -18,17 +26,25 @@ import dayjs from 'dayjs';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers';
-import { useNavigate } from 'react-router-dom/dist';
+import { useNavigate, useSearchParams } from 'react-router-dom/dist';
import { Field, Formik, Form } from 'formik';
import * as Yup from 'yup';
-import { useParams } from 'react-router-dom';
+import { useLocation, useParams } from 'react-router-dom';
import { addTestsLabel } from '../lib/helper.js';
+import CustomSnackbar from './custom/CustomSnackbar.jsx';
+import PriceCalculation from './PriceCalculation.jsx';
+import { ASSET_URL } from '../services/urls.js';
import TotalPrice from './forms/TotalPrice/TotalPrice.jsx';
-import { CLEAR_SEARCHED_AUDITOR } from '../redux/actions/types.js';
import Cookies from 'js-cookie';
import axios from 'axios';
import { API_URL } from '../services/urls.js';
import _ from 'lodash';
+import { CLEAR_SEARCHED_AUDITOR, CUSTOMER } from '../redux/actions/types.js';
+import { addUserInOrganization } from '../redux/actions/organizationAction.js';
+import { AUDITOR, CLEAR_SEARCH } from '../redux/actions/types.js';
+import { searchCustomers } from '../redux/actions/customerAction.js';
+import Radio from '@mui/material/Radio';
+import { getAuditors } from '../redux/actions/auditorAction.js';
export default function AuditorSearchModal({
open,
@@ -37,16 +53,26 @@ export default function AuditorSearchModal({
setState,
setError,
projectInfo,
+ invite,
+ modeType,
+ customer,
+ type = 'auditor',
}) {
const navigate = useNavigate();
const dispatch = useDispatch();
const { id } = useParams();
-
+ const auditorReducer = useSelector(state => state.auditor.auditors);
+ const customersReducer = useSelector(state => state.customer.customers);
const projectReducer = useSelector(state => state.project);
const customerReducer = useSelector(state => state.customer);
const [selectedAuditor, setSelectedAuditor] = useState({});
- const [mode, setMode] = useState('search');
+ const organization = useSelector(s => s.organization.organization);
+ const [rulesOfMember, setRulesOfMember] = useState('Representative');
+ const user = useSelector(s => s.user.user);
+ const [openDrop, setOpenDrop] = useState(false);
+ const [mode, setMode] = useState(modeType || 'search');
+ const [inputValue, setInputValue] = useState('');
const [query, setQuery] = useState('');
const [page, setPage] = useState(1);
const [auditors, setAuditors] = useState([]);
@@ -56,10 +82,66 @@ export default function AuditorSearchModal({
const [searchValue, setSearchValue] = useState('');
const scrollTimeout = useRef(null);
const listInnerRef = useRef();
+ const [auditorPagination, setAuditorPagination] = useState({
+ hasMore: true,
+ total: 0,
+ });
+ const [organizationPagination, setOrganizationPagination] = useState({
+ hasMore: true,
+ total: 0,
+ });
+
+ useEffect(() => {
+ dispatch(getAuditors(query, 15));
+ }, [query]);
+
+ useEffect(() => {
+ if (!modeType) {
+ if (organization.id) {
+ if (
+ organization.organization_type.toLowerCase() === AUDITOR.toLowerCase()
+ ) {
+ dispatch(getAuditors(query, 15));
+ } else {
+ dispatch(searchCustomers({ search: query, perPage: 15 }));
+ }
+ } else {
+ dispatch(getAuditors(query, 15));
+ }
+ }
+ return () => {
+ if (!modeType) {
+ dispatch({ type: CLEAR_SEARCH });
+ }
+ };
+ }, [query, organization.id]);
+
+ const handleInputChange = event => {
+ setQuery(event.target.value);
+ };
const handleOptionChange = option => {
setSelectedAuditor(option);
- setMode('offer');
+ if (invite) {
+ setMode('invite');
+ } else {
+ setMode('offer');
+ }
+ };
+
+ const handleInviteUser = () => {
+ const data = [
+ {
+ user_id: customer?.user_id ? customer.user_id : selectedAuditor.user_id,
+ access_level: rulesOfMember,
+ },
+ ];
+ dispatch(
+ addUserInOrganization(organization.link_id, data, organization.id),
+ );
+ setMode(modeType || 'search');
+ setQuery('');
+ handleClose();
};
const handleSearch = async () => {
@@ -69,10 +151,39 @@ export default function AuditorSearchModal({
await navigate(
`/auditors?search=${query}&projectIdToInvite=${id || projectInfo.id}`,
);
+ // if (setState) {
+ // await setState(true);
+ // }
+ // if (handleSubmit) {
+ // handleSubmit();
+ // }
+ // if (organization.id) {
+ // if (
+ // organization.organization_type.toLowerCase() === AUDITOR.toLowerCase()
+ // ) {
+ // await navigate(
+ // `/auditors?search=${query}&organization=${organization.link_id}`,
+ // {
+ // state: { from: location.pathname },
+ // },
+ // );
+ // } else {
+ // await navigate(
+ // `/customers?search=${query}&organization=${organization.link_id}`,
+ // {
+ // state: { from: location.pathname },
+ // },
+ // );
+ // }
+ // } else {
+ // await navigate(`/auditors?search=${query}&projectIdToInvite=${id}`, {
+ // state: { from: location.pathname },
+ // });
+ // }
};
useEffect(() => {
- const fetchAuditors = async () => {
+ const fetchResults = async () => {
try {
setIsLoading(true);
setPage(1);
@@ -83,87 +194,184 @@ export default function AuditorSearchModal({
setScrollPosition(listInnerRef.current.scrollTop);
}
- const response = await axios.get(
- `${API_URL}/search?query=${query}&sort_by=rating&tags=&sort_order=-1&page=1&per_page=15&kind=auditor badge`,
- { headers: { Authorization: `Bearer ${token}` } },
+ const requests = [
+ axios.get(
+ `${API_URL}/search?query=${query}&sort_by=rating&tags=&sort_order=-1&page=1&per_page=15&kind=auditor badge`,
+ { headers: { Authorization: `Bearer ${token}` } },
+ ),
+ axios.get(
+ `${API_URL}/search?query=${query}&sort_by=rating&tags=&sort_order=-1&page=1&per_page=15&kind=organization`,
+ { headers: { Authorization: `Bearer ${token}` } },
+ ),
+ ];
+
+ const [auditorsResponse, organizationsResponse] = await Promise.all(
+ requests,
);
- setAuditors(response.data.result);
+ // Update pagination info for both types
+ setAuditorPagination({
+ hasMore:
+ auditorsResponse.data.result.length > 0 &&
+ auditorsResponse.data.result.length <
+ auditorsResponse.data.totalDocuments,
+ total: auditorsResponse.data.totalDocuments,
+ });
+
+ setOrganizationPagination({
+ hasMore:
+ organizationsResponse.data.result.length > 0 &&
+ organizationsResponse.data.result.length <
+ organizationsResponse.data.totalDocuments,
+ total: organizationsResponse.data.totalDocuments,
+ });
+
+ const combinedResults = [
+ ...auditorsResponse.data.result,
+ ...organizationsResponse.data.result,
+ ];
+
+ setAuditors(combinedResults);
} catch (error) {
- console.error('Error fetching auditors:', error);
+ console.error('Error fetching results:', error);
} finally {
setIsLoading(false);
}
};
if (query) {
- fetchAuditors();
+ fetchResults();
}
}, [query]);
useEffect(() => {
- if (!isLoading && listInnerRef.current && scrollPosition > 0) {
- requestAnimationFrame(() => {
- listInnerRef.current.scrollTop = scrollPosition;
- });
- }
- }, [isLoading, auditors]);
-
- useEffect(() => {
- const fetchMoreAuditors = async () => {
- if (isLoading || lastList) return;
+ const fetchMoreResults = async () => {
+ if (
+ isLoading ||
+ (!auditorPagination.hasMore && !organizationPagination.hasMore)
+ )
+ return;
try {
setIsLoading(true);
const token = Cookies.get('token');
- const response = await axios.get(
- `${API_URL}/search?query=${query}&sort_by=rating&tags=&sort_order=-1&page=${page}&per_page=15&kind=auditor badge`,
- { headers: { Authorization: `Bearer ${token}` } },
- );
- if (response.data.result.length === 0) {
+ const requests = [];
+
+ // Only fetch auditors if there are more to fetch
+ if (auditorPagination.hasMore) {
+ requests.push(
+ axios.get(
+ `${API_URL}/search?query=${query}&sort_by=rating&tags=&sort_order=-1&page=${page}&per_page=15&kind=auditor badge`,
+ { headers: { Authorization: `Bearer ${token}` } },
+ ),
+ );
+ }
+
+ // Only fetch organizations if there are more to fetch
+ if (organizationPagination.hasMore) {
+ requests.push(
+ axios.get(
+ `${API_URL}/search?query=${query}&sort_by=rating&tags=&sort_order=-1&page=${page}&per_page=15&kind=organization`,
+ { headers: { Authorization: `Bearer ${token}` } },
+ ),
+ );
+ }
+
+ if (requests.length === 0) {
+ setLastList(true);
+ return;
+ }
+
+ const responses = await Promise.all(requests);
+ let newResults = [];
+
+ responses.forEach((response, index) => {
+ const isAuditorResponse = auditorPagination.hasMore && index === 0;
+ const isOrgResponse =
+ organizationPagination.hasMore &&
+ index === (auditorPagination.hasMore ? 1 : 0);
+
+ if (isAuditorResponse) {
+ setAuditorPagination(prev => ({
+ ...prev,
+ hasMore:
+ response.data.result.length > 0 &&
+ page * 15 < response.data.totalDocuments,
+ }));
+ }
+
+ if (isOrgResponse) {
+ setOrganizationPagination(prev => ({
+ ...prev,
+ hasMore:
+ response.data.result.length > 0 &&
+ page * 15 < response.data.totalDocuments,
+ }));
+ }
+
+ newResults = [...newResults, ...response.data.result];
+ });
+
+ if (newResults.length === 0) {
setLastList(true);
return;
}
setAuditors(prev => {
- const newAuditors = response.data.result;
- const uniqueAuditors = [...prev];
+ const uniqueResults = [...prev];
- newAuditors.forEach(newAuditor => {
+ newResults.forEach(newItem => {
if (
- !uniqueAuditors.some(
- existing => existing.user_id === newAuditor.user_id,
+ !uniqueResults.some(
+ existing =>
+ existing.user_id === newItem.user_id ||
+ existing.id === newItem.id,
)
) {
- uniqueAuditors.push(newAuditor);
+ uniqueResults.push(newItem);
}
});
- return uniqueAuditors;
+ return uniqueResults;
});
} catch (error) {
- console.error('Error fetching more auditors:', error);
+ console.error('Error fetching more results:', error);
} finally {
setIsLoading(false);
}
};
if (page > 1) {
- fetchMoreAuditors();
+ fetchMoreResults();
}
- }, [page, query, lastList]);
+ }, [
+ page,
+ query,
+ lastList,
+ auditorPagination.hasMore,
+ organizationPagination.hasMore,
+ ]);
const handleScroll = useCallback(
_.throttle(e => {
- if (!isLoading && !lastList) {
+ if (
+ !isLoading &&
+ !lastList &&
+ (auditorPagination.hasMore || organizationPagination.hasMore)
+ ) {
const { scrollTop, scrollHeight, clientHeight } = e.target;
if (scrollHeight - scrollTop <= clientHeight * 1.2) {
setPage(prev => prev + 1);
}
}
}, 300),
- [isLoading, lastList, page],
+ [
+ isLoading,
+ lastList,
+ auditorPagination.hasMore,
+ organizationPagination.hasMore,
+ ],
);
const handleSearchInput = e => {
@@ -181,6 +389,42 @@ export default function AuditorSearchModal({
}, 300);
};
+ // const renderSearchResult = item => {
+ // const isOrganization = type === 'organization';
+ // return (
+ // handleOptionChange(item)}
+ // >
+ //
+ //
+ //
+ // {isOrganization
+ // ? item.name
+ // : `${item.first_name} ${item.last_name}`}
+ //
+ // {item.description && (
+ //
+ // {item.description}
+ //
+ // )}
+ //
+ //
+ // );
+ // };
+
return (
);
}
@@ -396,6 +795,18 @@ const MakeOfferSchema = Yup.object().shape({
}),
});
+const roleDescriptionTitle = theme => ({
+ fontSize: '16px',
+ color: '#9f9f9f',
+ marginLeft: '42px',
+ [theme.breakpoints.down('md')]: {
+ fontSize: '12px',
+ },
+ [theme.breakpoints.down('xs')]: {
+ fontSize: '10px',
+ },
+});
+
const userListSx = theme => ({
position: 'fixed',
top: 'auto',
diff --git a/src/components/Chat/ChatList.jsx b/src/components/Chat/ChatList.jsx
index 6c6e0f18..4b250f7b 100644
--- a/src/components/Chat/ChatList.jsx
+++ b/src/components/Chat/ChatList.jsx
@@ -14,11 +14,14 @@ import ChatListItem from './ChatListItem.jsx';
import { AUDITOR, CUSTOMER } from '../../redux/actions/types.js';
import { searchAuditor } from '../../redux/actions/auditorAction.js';
import { searchCustomers } from '../../redux/actions/customerAction.js';
+import { searchOrganization } from '../../redux/actions/organizationAction.js';
+import theme from '../../styles/themes.js';
-const ChatList = ({ chatList, chatListIsOpen, setChatListIsOpen }) => {
+const ChatList = ({ chatList, chatListIsOpen, setChatListIsOpen, orgId }) => {
const dispatch = useDispatch();
const { auditors } = useSelector(s => s.auditor);
const { customers } = useSelector(s => s.customer);
+ const { searchOrganizations } = useSelector(s => s.organization);
const { user } = useSelector(s => s.user);
const [, startTransition] = useTransition();
@@ -33,87 +36,92 @@ const ChatList = ({ chatList, chatListIsOpen, setChatListIsOpen }) => {
if (search.trim()) {
dispatch(searchAuditor({ search, perPage: 20 }, false));
dispatch(searchCustomers({ search, perPage: 20 }));
+ dispatch(searchOrganization({ search, perPage: 20 }));
}
});
}, [search]);
return (
- <>
-
-
-
-
-
-
- ),
- }}
- />
- setChatListIsOpen(false)}
- color="inherit"
- sx={closeButtonSx}
- >
-
-
-
- {/**/}
- {/* Chats*/}
- {/**/}
+
+
+
+
+
+
+ ),
+ }}
+ />
+ setChatListIsOpen(false)}
+ color="inherit"
+ sx={closeButtonSx}
+ >
+
+
+ {/**/}
+ {/* Chats*/}
+ {/**/}
+
-
- {chatList.length > 0
- ? chatList
- ?.filter(chat =>
- chat.name
- ?.toLowerCase()
- .includes(search.toLowerCase().trim()),
- )
- .reverse()
- .map(chat => (
+
+ {chatList.length > 0
+ ? chatList
+ ?.filter(chat =>
+ chat.name?.toLowerCase().includes(search.toLowerCase().trim()),
+ )
+ .reverse()
+ .map(chat => {
+ // const org = chat?.members.find(org => org.org_user_id);
+ //
+ return (
- ))
- : !search && (
- You haven't written to anyone yet
- )}
+ );
+ })
+ : !search && (
+ You haven't written to anyone yet
+ )}
- {search &&
- auditors
- .filter(
- auditor =>
- !chatList.some(chat =>
- chat.members.some(
- member =>
- member.id === auditor.user_id &&
- member.role?.toLowerCase() === AUDITOR,
- ),
- ) && auditor.user_id !== user.id,
- )
- .map(auditor => (
+ {search &&
+ auditors
+ .filter(
+ auditor =>
+ !chatList.some(chat =>
+ chat.members.some(
+ member =>
+ member.id === auditor.user_id &&
+ member.role?.toLowerCase() === AUDITOR,
+ ),
+ ) && auditor.user_id !== user.id,
+ )
+ .map(auditor => {
+ return (
{
members: [{ id: auditor.user_id }, { id: user.id }],
}}
/>
- ))}
+ );
+ })}
- {search &&
- customers
- .filter(
- customer =>
- !chatList.some(chat =>
- chat.members.some(
- member =>
- member.id === customer.user_id &&
- member.role?.toLowerCase() === CUSTOMER,
- ),
- ) && customer.user_id !== user.id,
- )
- .map(customer => (
+ {search &&
+ customers
+ .filter(
+ customer =>
+ !chatList.some(chat =>
+ chat.members.some(
+ member =>
+ member.id === customer.user_id &&
+ member.role?.toLowerCase() === CUSTOMER,
+ ),
+ ) && customer.user_id !== user.id,
+ )
+ .map(customer => (
+
+ ))}
+
+ {search &&
+ searchOrganizations
+ .filter(
+ organization =>
+ !chatList.some(chat =>
+ chat.members.some(
+ member =>
+ member.id === organization.user_id &&
+ member.role?.toLowerCase() === CUSTOMER,
+ ),
+ ) && organization.user_id !== user.id,
+ )
+ .map(organization => {
+ return (
- ))}
+ );
+ })}
- {chatList.length > 0 &&
- !customers.length &&
- !auditors.length &&
- !chatList.find(chat =>
- chat.name?.toLowerCase().includes(search.toLowerCase().trim()),
- ) && No search results}
-
+ {chatList.length > 0 &&
+ !customers.length &&
+ !auditors.length &&
+ !chatList.find(chat =>
+ chat.name?.toLowerCase().includes(search.toLowerCase().trim()),
+ ) && No search results}
- setChatListIsOpen(false)}
- />
- >
+
);
};
export default ChatList;
-const wrapper = theme => ({
- width: '30%',
+const wrapper = (theme, openOrgList) => ({
borderRight: '2px solid #e5e5e5',
display: 'flex',
flexDirection: 'column',
- [theme.breakpoints.down('xs')]: {
- display: 'none',
- },
+ width: '100%',
+ // width: `calc(100% - ${!openOrgList ? '70px' : '0px'} )`,
+ // width: `30%`,
+ // [theme.breakpoints.down('md')]: {
+ // width: `calc(100% - ${!openOrgList ? '73px' : '0px'})`,
+ // },
+ // [theme.breakpoints.down('xs')]: {
+ // display: 'none',
+ // },
});
const mobileChatListOpen = theme => ({
diff --git a/src/components/Chat/ChatListItem.jsx b/src/components/Chat/ChatListItem.jsx
index ab04d08c..2fea2d3c 100644
--- a/src/components/Chat/ChatListItem.jsx
+++ b/src/components/Chat/ChatListItem.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Link as RouterLink } from 'react-router-dom';
+import { Link as RouterLink, useParams } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { Avatar, Box, Link, Tooltip } from '@mui/material';
import { ASSET_URL } from '../../services/urls.js';
@@ -16,14 +16,22 @@ const ChatListItem = ({
isNew = false,
userDataId = false,
role,
+ orgId,
}) => {
const dispatch = useDispatch();
+ const { id } = useParams();
const getUnreadForUser = chat =>
chat.unread.find(unread => unread.id === user.id)?.unread || 0;
- const getRole = () =>
- role || chat.members.find(member => member.id !== user.id)?.role;
+ const getRole = () => {
+ return (
+ role ||
+ chat.members.find(
+ member => (member?.org_user?.id ?? member.id) !== user.id,
+ )?.role
+ );
+ };
const setChatHandle = () => {
setListIsOpen(false);
@@ -52,9 +60,9 @@ const ChatListItem = ({
return (
diff --git a/src/components/Chat/CurrentChat.jsx b/src/components/Chat/CurrentChat.jsx
index 80a2f1be..57ddcb76 100644
--- a/src/components/Chat/CurrentChat.jsx
+++ b/src/components/Chat/CurrentChat.jsx
@@ -15,10 +15,12 @@ import {
chatSendMessage,
closeCurrentChat,
getChatList,
+ getChatListByOrg,
getChatMessages,
} from '../../redux/actions/chatActions.js';
import AttachFileModal from './AttachFileModal.jsx';
import Headings from '../../router/Headings.jsx';
+import { useSearchParams } from 'react-router-dom/dist';
const CurrentChat = ({
chatMessages,
@@ -30,7 +32,8 @@ const CurrentChat = ({
const navigate = useNavigate();
const { id } = useParams();
const { user } = useSelector(s => s.user);
-
+ const [searchParams] = useSearchParams();
+ const orgId = searchParams.get('org');
const [newMessage, setNewMessage] = useState('');
const [attachModalIsOpen, setAttachModalIsOpen] = useState(false);
const [displayedMessages, setDisplayedMessages] = useState(20);
@@ -43,6 +46,9 @@ const CurrentChat = ({
const messageBoxRef = useRef();
const newMessagesTextRef = useRef();
const userLinkDataRef = useRef({});
+ const organization = useSelector(s =>
+ s.organization.organizations.find(el => el.id === orgId),
+ );
useEffect(() => {
if (
@@ -54,12 +60,17 @@ const CurrentChat = ({
setChatId(currentChat?.chatId);
dispatch(getChatMessages(currentChat.chatId, user.id));
}
- }, [currentChat, chatId]);
+ }, [currentChat, chatId, orgId]);
useEffect(() => {
if (currentChat?.chatId && id !== currentChat?.chatId) {
- navigate(`/chat/${currentChat?.chatId}`);
- dispatch(getChatList(user.current_role));
+ if (!orgId) {
+ navigate(`/chat/${currentChat?.chatId}`);
+ dispatch(getChatList(user.current_role));
+ } else {
+ navigate(`/chat/${currentChat?.chatId}?org=${orgId}`);
+ dispatch(getChatListByOrg('Organization', orgId));
+ }
}
}, [currentChat?.chatId]);
@@ -131,12 +142,22 @@ const CurrentChat = ({
const handleSend = () => {
if (!newMessage.trim()) return;
+ // dispatch(
+ // chatSendMessage(
+ // newMessage.trim(),
+ // { id: currentChat?.chatId, role: currentChat?.role },
+ // user.current_role,
+ // currentChat?.isNew,
+ // ),
+ // );
dispatch(
chatSendMessage(
newMessage.trim(),
{ id: currentChat?.chatId, role: currentChat?.role },
- user.current_role,
+ orgId ? 'Organization' : user.current_role,
currentChat?.isNew,
+ 'Text',
+ orgId && orgId,
),
);
setNewMessage('');
@@ -243,22 +264,28 @@ const CurrentChat = ({
)}
{!currentChat?.isNew ? (
- chatMessages.slice(getDisplayedMessages()).map((msg, idx, ar) => {
+ chatMessages
+ .slice(getDisplayedMessages())
+ .map((msg, idx, ar) => {
const date = new Date(msg?.time / 1000).toDateString();
const prevMsgDate = new Date(
ar[idx - 1]?.time / 1000,
).toDateString();
const unreadLabel = !!unread && ar.length - unread === idx;
const isInterlocutorRead = idx < ar.length - interlocutorUnread;
-
return (
-
+
{date !== prevMsgDate && (
{date.replace(/[^ ]+/, '')}
)}
{unreadLabel && New messages:}
({
width: '70%',
display: 'flex',
flexDirection: 'column',
- [theme.breakpoints.down('sm')]: {
+ [theme.breakpoints.down('xs')]: {
width: '100%',
},
});
diff --git a/src/components/Chat/Message.jsx b/src/components/Chat/Message.jsx
index 017d07b0..6363d2ee 100644
--- a/src/components/Chat/Message.jsx
+++ b/src/components/Chat/Message.jsx
@@ -9,8 +9,19 @@ import { AUDITOR, CUSTOMER } from '../../redux/actions/types.js';
import ImageMessage from './ImageMessage.jsx';
import AuditMessage from './AuditMessage.jsx';
import theme from '../../styles/themes.js';
+import { Link, useLocation } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom/dist';
-const Message = ({ message, user, currentChat, isRead, previousMessage }) => {
+const Message = ({
+ message,
+ user,
+ currentChat,
+ isRead,
+ type,
+ orgId,
+ chatRole,
+ previousMessage
+}) => {
const { customer } = useSelector(state => state.customer);
const { auditor } = useSelector(state => state.auditor);
@@ -25,6 +36,8 @@ const Message = ({ message, user, currentChat, isRead, previousMessage }) => {
fileMessage = {};
}
+ const location = useLocation();
+ const navigate = useNavigate();
const userAvatar = useMemo(() => {
if (user.current_role === AUDITOR && !!auditor?.avatar) {
return auditor.avatar;
@@ -38,12 +51,20 @@ const Message = ({ message, user, currentChat, isRead, previousMessage }) => {
}
}, [user.current_role, customer?.avatar, auditor?.avatar]);
+ const isOwn = () => {
+ return message.from.role.toLowerCase() === 'organization'
+ ? { isOwn: message.from?.org_user?.id === user.id }
+ : { isOwn: message.from?.id === user.id };
+ };
+
const getMessageAvatar = () => {
+ const avatar = isOwn() ? userAvatar : currentChat?.avatar;
if (message?.from?.id === user?.id) {
return userAvatar ? `${ASSET_URL}/id/${userAvatar}` : null;
}
return currentChat?.avatar ? `${ASSET_URL}/id/${currentChat.avatar}` : null;
};
+ // }
const downloadFile = () => {
const token = Cookies.get('token');
@@ -66,8 +87,17 @@ const Message = ({ message, user, currentChat, isRead, previousMessage }) => {
const shouldShowAvatar =
!previousMessage || previousMessage.from?.id !== message.from?.id;
+ const handleGoProfile = () => {
+ localStorage.setItem('prev', location.pathname);
+ navigate(
+ `/${message.from?.org_user?.role[0].toLowerCase()}/${
+ message.from?.org_user?.id
+ }`,
+ );
+ };
+
return (
-
+
{shouldShowAvatar ? (
{
+ {message.from?.org_user?.id && (
+
+ {message.from?.org_user.name}
+
+ )}
{message.kind === 'Image' ? (
) : message.kind === 'Audit' ? (
@@ -193,6 +230,16 @@ const messageSx = ({ isOwn }) => ({
'&:hover .avatar-plug': {
opacity: 1,
},
+ '& a': {
+ textDecoration: 'unset',
+ },
+});
+
+const orgNameSx = (theme, color) => ({
+ padding: '5px!important',
+ paddingLeft: '18px!important',
+ color: `${color}!important`,
+ cursor: 'pointer',
});
const avatarPlugSx = theme => ({
diff --git a/src/components/Chat/TypeChat.jsx b/src/components/Chat/TypeChat.jsx
new file mode 100644
index 00000000..727601a9
--- /dev/null
+++ b/src/components/Chat/TypeChat.jsx
@@ -0,0 +1,228 @@
+import React, { useMemo, useState } from 'react';
+import {
+ Avatar,
+ Box,
+ Button,
+ Divider,
+ List,
+ ListItem,
+ ListItemAvatar,
+ ListItemText,
+ Popover,
+ Typography,
+} from '@mui/material';
+import { addTestsLabel } from '../../lib/helper.js';
+import ChatIcon from '../icons/ChatIcon.jsx';
+import { AUDITOR, CUSTOMER } from '../../redux/actions/types.js';
+import { setCurrentChat } from '../../redux/actions/chatActions.js';
+import { useDispatch, useSelector } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+import { ASSET_URL } from '../../services/urls.js';
+
+const TypeChat = ({ auditor, project }) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [anchorEl, setAnchorEl] = useState(null);
+ const { chatList } = useSelector(s => s.chat);
+ const { user } = useSelector(s => s.user);
+ const { organizations } = useSelector(s => s.organization);
+ const myAuditor = useSelector(s => s.auditor.auditor);
+ const myCustomer = useSelector(s => s.customer.customer);
+ const navigate = useNavigate();
+ const dispatch = useDispatch();
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ const open = Boolean(anchorEl);
+ const id = open ? 'simple-popover' : undefined;
+
+ const startChat = () => {
+ window.scrollTo(0, 0);
+
+ const existingChat = chatList.find(chat =>
+ chat.members?.find(
+ member =>
+ member.id === auditor?.user_id &&
+ member.role?.toLowerCase() === AUDITOR,
+ ),
+ );
+ const chatId = existingChat ? existingChat.id : auditor?.user_id;
+ const members = [auditor?.user_id, user.id];
+
+ dispatch(
+ setCurrentChat(chatId, {
+ name: auditor.first_name,
+ avatar: auditor.avatar,
+ role: AUDITOR,
+ isNew: !existingChat,
+ members,
+ }),
+ );
+ localStorage.setItem('path', window.location.pathname);
+ navigate(`/chat/${existingChat ? existingChat.id : auditor?.user_id}`);
+ };
+
+ const myUser = useMemo(() => {
+ if (user?.current_role?.toLowerCase() === AUDITOR.toLowerCase()) {
+ return myAuditor;
+ } else {
+ return myCustomer;
+ }
+ }, [myAuditor, myCustomer]);
+
+ const handleSendMessage = e => {
+ // TODO add check for pm or org chat
+ if (organizations.length) {
+ setAnchorEl(e.currentTarget);
+ } else {
+ startChat();
+ }
+ };
+
+ const chatFromOrg = org => {
+ console.log(org);
+ window.scrollTo(0, 0);
+
+ if (auditor) {
+ const existingChat = chatList.find(chat =>
+ chat.members?.find(
+ member =>
+ member.id === auditor?.user_id &&
+ member.role?.toLowerCase() === AUDITOR,
+ ),
+ );
+
+ const chatId = existingChat ? existingChat.id : auditor?.user_id;
+ const members = [auditor?.user_id, user.id];
+
+ dispatch(
+ setCurrentChat(chatId, {
+ name: auditor.first_name,
+ avatar: auditor.avatar,
+ role: AUDITOR,
+ isNew: !existingChat,
+ members,
+ }),
+ );
+ localStorage.setItem('path', window.location.pathname);
+ navigate(
+ `/chat/${existingChat ? existingChat.id : auditor?.user_id}?org=${
+ org.id
+ }`,
+ );
+ } else if (project) {
+ const existingChat = chatList.find(chat =>
+ chat.members?.find(
+ member =>
+ member.id === project?.customer_id &&
+ member.role?.toLowerCase() === CUSTOMER,
+ ),
+ );
+ const chatId = existingChat ? existingChat.id : project?.customer_id;
+ const members = [project?.customer_id, user.id];
+
+ dispatch(
+ setCurrentChat(chatId, {
+ role: CUSTOMER,
+ isNew: !existingChat,
+ userDataId: project?.customer_id,
+ members,
+ }),
+ );
+ localStorage.setItem('path', window.location.pathname);
+ navigate(`/chat/${project?.customer_id}?org=${org.id}`);
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ {organizations.map(org => {
+ return (
+
+ chatFromOrg(org)}
+ sx={{
+ cursor: 'pointer',
+ alignItems: 'center',
+ '&:hover': {
+ backgroundColor: '#e8e8e8',
+ },
+ }}
+ >
+
+
+
+
+ // {name}
+ //
+ // }
+ />
+
+
+
+ );
+ })}
+
+
+ >
+ );
+};
+
+export default TypeChat;
diff --git a/src/components/CreateProjectCard.jsx b/src/components/CreateProjectCard.jsx
index 1b44c99c..6dd32e62 100644
--- a/src/components/CreateProjectCard.jsx
+++ b/src/components/CreateProjectCard.jsx
@@ -289,6 +289,33 @@ const CreateProjectCard = ({ projectInfo }) => {
};
}, []);
+ useEffect(() => {
+ if (initialValues?.id && initialValues.scope.length) {
+ const getRepoUrl = initialValues.scope[0];
+ function getShaFromGitHubUrl(url) {
+ const regex = /\/blob\/([0-9a-f]{40})\//;
+ const match = url.match(regex);
+ return match ? match[1] : null;
+ }
+
+ function parseGitHubUrl(gitHubUrl) {
+ const urlParts = gitHubUrl.split('/');
+ const owner = urlParts[3];
+ const repo = urlParts[4];
+
+ return `${owner}/${repo}`;
+ }
+
+ const githubRepo = parseGitHubUrl(getRepoUrl);
+ const sha = getShaFromGitHubUrl(getRepoUrl);
+ dispatch(getSha(sha));
+ dispatch(getRepoOwner(githubRepo));
+ }
+ return () => {
+ dispatch({ type: CLEAR_PROJECT });
+ };
+ }, []);
+
return (
{
setState={setState}
setError={setError}
projectInfo={project}
+ type={'auditor'}
/>
{
+ const navigate = useNavigate();
+ const user = useSelector(state => state.user.user);
+ const [openModal, setOpenModal] = useState(false);
+ const customerReducer = useSelector(state => state.customer.customer);
+ const [message, setMessage] = useState('');
+ const [isForm, setIsForm] = useState(false);
+ const myProjects = useSelector(state => state.project.myProjects);
+ const dispatch = useDispatch();
+ const userProjects = useSelector(s => s.project.myProjects);
+ const [errorMessage, setErrorMessage] = useState(null);
+ const [showAddUser, setShowAddUser] = useState(false);
+
+ const handleView = () => {
+ setOpenModal(true);
+ };
+
+ const handleCloseModal = () => {
+ setOpenModal(false);
+ };
+
+ const handleError = () => {
+ setErrorMessage(null);
+ setMessage('Switched to customer role');
+ const delayedFunc = setTimeout(() => {
+ if (userProjects.length) {
+ navigate(`/my-projects/${auditor.user_id}`);
+ } else {
+ setMessage(null);
+ setErrorMessage('No active projects');
+ }
+ }, 1000);
+ return () => clearTimeout(delayedFunc);
+ };
+
+ const handleInvite = () => {
+ setShowAddUser(!showAddUser);
+ };
+
+ return (
+
+ {
+ setErrorMessage(null);
+ setMessage(null);
+ }}
+ severity={isForm || message ? 'success' : 'error'}
+ text={message || errorMessage}
+ />
+ {
+ setShowAddUser(false);
+ }}
+ customer={customer}
+ modeType={'invite'}
+ setError={() => console.log('error')}
+ />
+
+
+
+
+
+
+
+
+
+
+ {customer.first_name} {customer.last_name}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/**/}
+ {/* View more*/}
+ {/**/}
+
+ Invite
+
+ {budge && not registered}
+
+
+ );
+};
+
+export default CustomerListCard;
+
+const budgeTitle = theme => ({
+ color: '#B2B3B3',
+ fontSize: '12px!important',
+ marginTop: '-10px',
+ [theme.breakpoints.down('sm')]: {
+ fontSize: '10px!important',
+ marginTop: '-5px',
+ },
+});
+
+const wrapper = theme => ({
+ padding: '12px 20px 12px 45px',
+ display: 'flex',
+ gap: '10px',
+ height: '100%',
+ justifyContent: 'space-between',
+ [theme.breakpoints.down('lg')]: {
+ padding: '20px',
+ },
+ [theme.breakpoints.down('xs')]: {
+ padding: '15px',
+ },
+});
+
+const cardLeftSide = {
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ gap: '12px',
+ [theme.breakpoints.down('xs')]: {
+ gap: '15px',
+ },
+};
+
+const cardRightSide = {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ gap: '15px',
+ [theme.breakpoints.down('xs')]: {
+ gap: '12px',
+ },
+};
+
+const avatarDescription = theme => ({
+ display: 'flex',
+ flexDirection: 'row',
+ gap: '30px',
+ [theme.breakpoints.down('lg')]: {
+ gap: '20px',
+ },
+ [theme.breakpoints.down('xs')]: {
+ gap: '10px',
+ },
+});
+
+const descriptionStyle = theme => ({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '15px',
+ [theme.breakpoints.down('xs')]: {
+ gap: '8px',
+ },
+});
+
+const avatarStyle = theme => ({
+ width: '65px',
+ height: '65px',
+ [theme.breakpoints.down('xs')]: {
+ width: '38px',
+ height: '38px',
+ },
+});
+
+const nameStyle = {
+ fontWeight: '600',
+ fontSize: {
+ zero: '11px',
+ sm: '14px',
+ md: '16px',
+ lg: '18px',
+ },
+ color: '#152BEA',
+};
+
+const inviteButtonStyle = theme => ({
+ width: '130px',
+ textTransform: 'unset',
+ boxShadow: '0',
+ fontWeight: 600,
+ [theme.breakpoints.down('md')]: {
+ width: '130px',
+ },
+ [theme.breakpoints.down('sm')]: {
+ width: '86px',
+ fontSize: '8px',
+ },
+});
+
+const tagsWrapper = theme => ({
+ [theme.breakpoints.down('xs')]: {
+ maxWidth: '130px',
+ },
+});
diff --git a/src/components/IdentitySetting/IdentitySetting.jsx b/src/components/IdentitySetting/IdentitySetting.jsx
index b2ad2062..c8a43bb1 100644
--- a/src/components/IdentitySetting/IdentitySetting.jsx
+++ b/src/components/IdentitySetting/IdentitySetting.jsx
@@ -250,6 +250,9 @@ const buttonSx = theme => ({
fontSize: '18px',
width: '214px',
borderRadius: '10px',
+ [theme.breakpoints.down(850)]: {
+ width: '234px',
+ },
[theme.breakpoints.down('xs')]: {
padding: '9px 10px',
},
diff --git a/src/components/Organization.jsx b/src/components/Organization.jsx
new file mode 100644
index 00000000..b098dd13
--- /dev/null
+++ b/src/components/Organization.jsx
@@ -0,0 +1,602 @@
+import React, { useMemo, useEffect, useState } from 'react';
+import {
+ Avatar,
+ Box,
+ Button,
+ Typography,
+ Link,
+ useMediaQuery,
+ Tooltip,
+ ListItemText,
+ List,
+ ListItem,
+ Checkbox,
+ ListItemAvatar,
+ Chip,
+ Modal,
+ FormControlLabel,
+} from '@mui/material';
+import GitHubIcon from '@mui/icons-material/GitHub.js';
+import ArrowBackIcon from '@mui/icons-material/ArrowBack.js';
+import theme from '../styles/themes.js';
+import { useNavigate, useParams } from 'react-router-dom/dist';
+import { useDispatch, useSelector } from 'react-redux';
+import Loader from './Loader.jsx';
+import { AUDITOR, CUSTOMER } from '../redux/actions/types.js';
+import TagsList from './tagsList';
+import { ASSET_URL } from '../services/urls.js';
+import MobileTagsList from './MobileTagsList/index.jsx';
+import { addTestsLabel, capitalize, isAuth } from '../lib/helper.js';
+import ShareProfileButton from './custom/ShareProfileButton.jsx';
+import IdentitySetting from './IdentitySetting/IdentitySetting.jsx';
+import LinkedinIcon from './icons/LinkedinIcon.jsx';
+import XTwitterLogo from './icons/XTwitter-logo.jsx';
+import { clearUserMessages } from '../redux/actions/userAction.js';
+import CustomSnackbar from './custom/CustomSnackbar.jsx';
+import Headings from '../router/Headings.jsx';
+import ListItemButton from '@mui/material/ListItemButton';
+import DeleteForeverRoundedIcon from '@mui/icons-material/DeleteForeverRounded';
+import Layout from '../styles/Layout.jsx';
+import { CustomCard } from './custom/Card.jsx';
+import {
+ acceptInvites,
+ clearOrganization,
+ deleteInvites,
+ deleteUserFromOrganization,
+ getOrganizationById,
+} from '../redux/actions/organizationAction.js';
+import InfoCard from './custom/info-card.jsx';
+import AuditorSearchModal from './AuditorSearchModal.jsx';
+import ConfirmModal from './modal/ConfirmModal.jsx';
+import EditIcon from '@mui/icons-material/Edit';
+import UserLIstItem from './UserListItem/UserLIstItem.jsx';
+import { useLocation } from 'react-router-dom';
+
+const Organization = ({ linkId }) => {
+ const role = useSelector(s => s.user.user.current_role);
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const matchXs = useMediaQuery(theme.breakpoints.down('xs'));
+ const matchXxs = useMediaQuery(theme.breakpoints.down(590));
+ const organization = useSelector(s => s.organization.organization);
+ const invites = useSelector(s => s.organization.invites);
+ const [showAddUser, setShowAddUser] = useState(false);
+ const [openConfirm, setIsOpenConfirm] = useState(false);
+ const location = useLocation();
+
+ const {
+ customer,
+ error: customerError,
+ successMessage: successMessage,
+ } = useSelector(s => s.organization);
+ const {
+ auditor,
+ error: auditorError,
+ success: auditorSuccess,
+ } = useSelector(s => s.auditor);
+ const { user, error } = useSelector(s => s.user);
+
+ useEffect(() => {
+ dispatch(getOrganizationById(linkId));
+ return () => {
+ dispatch(clearOrganization());
+ };
+ }, [linkId]);
+
+ const handleEdit = () => {
+ navigate(`/edit-organization/${linkId}`);
+ };
+
+ const handleAddUser = () => {
+ setShowAddUser(!showAddUser);
+ };
+
+ const inviteMe = useMemo(() => {
+ return !!invites.find(el => el.id === organization.id);
+ }, [organization.id, invites]);
+
+ const isMyOrg = useMemo(() => {
+ return !!(
+ isAuth() &&
+ organization?.id &&
+ organization?.owner.user_id === user.id
+ );
+ }, [organization]);
+ if (!organization.id) {
+ return (
+
+
+
+
+
+
+ );
+ } else {
+ return (
+
+
+
+ navigate(location.state?.from || -1, { replace: true })
+ }
+ >
+
+
+
+ {
+ setShowAddUser(false);
+ }}
+ setError={() => console.log('error')}
+ type={
+ organization?.organization_type?.toLowerCase() ===
+ AUDITOR.toLowerCase()
+ ? 'auditor'
+ : 'organization'
+ }
+ />
+
+ {/**/}
+
+ dispatch(clearUserMessages())}
+ />
+
+
+
+
+ {organization.name}
+
+
+
+
+
+ Name
+ {organization.name}
+
+
+ Telegram
+
+ {organization.contacts?.telegram}
+
+
+
+ E-mail
+
+ {organization.contacts?.email}
+
+
+
+ Type
+
+ {organization.organization_type}
+
+
+
+
+
+
+ {organization.members.map(value => {
+ const labelId = `checkbox-list-secondary-label-${value}`;
+ return (
+
+
+
+ );
+ })}
+
+
+
+
+
+
+ {/*{!matchXs && (*/}
+ {/* */}
+ {/*)}*/}
+ {/*{matchXs && }*/}
+ {/**/}
+ {/* {user.linked_accounts?.map(account => {*/}
+ {/* if (account.name.toLowerCase() === 'linkedin') {*/}
+ {/* return (*/}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* );*/}
+ {/* } else if (account.name.toLowerCase() === 'github') {*/}
+ {/* return (*/}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* );*/}
+ {/* } else {*/}
+ {/* return (*/}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* );*/}
+ {/* }*/}
+ {/* })}*/}
+ {/**/}
+ {isAuth() && isMyOrg && (
+
+
+ Add user
+
+
+ Edit
+
+ {/**/}
+
+ )}
+ {inviteMe && !isMyOrg && (
+
+ dispatch(acceptInvites(organization.id))}
+ {...addTestsLabel('user_edit-button')}
+ >
+ Accept
+
+ setIsOpenConfirm(true)}
+ {...addTestsLabel('user_edit-button')}
+ >
+ Decline
+
+
+ )}
+ {
+ dispatch(deleteInvites(organization.id, user.id));
+ setIsOpenConfirm(false);
+ }}
+ handleDisagree={() => setIsOpenConfirm(false)}
+ isOpen={openConfirm}
+ />
+
+
+
+
+ );
+ }
+};
+
+export default Organization;
+
+const buttonsWrapper = theme => ({
+ display: 'flex',
+ gap: '20px',
+ alignItems: 'center',
+ justifyContent: 'center',
+ '& button': {
+ margin: 'unset',
+ },
+ [theme.breakpoints.down(630)]: {
+ flexDirection: 'column',
+ },
+});
+
+const wrapper = theme => ({
+ display: 'flex',
+ borderColor: 'grey',
+ flexDirection: 'column',
+ width: '100%',
+ '& ul': {
+ fontSize: '16px',
+ marginBottom: '28px',
+ '& li': {
+ marginLeft: '15px',
+ marginTop: '7px',
+ },
+ },
+ position: 'relative',
+ padding: '25px 30px 60px',
+ [theme.breakpoints.down('md')]: {
+ padding: '20px 24px 20px',
+ },
+ [theme.breakpoints.down('sm')]: {
+ gap: '20px',
+ padding: '30px 10px 20px',
+ '& h3': {
+ fontSize: '20px',
+ },
+ },
+ [theme.breakpoints.down(780)]: {
+ borderRadius: '0!important',
+ },
+});
+
+const aboutWrapper = theme => ({
+ width: '100%',
+ '& span': {
+ marginRight: '50px',
+ maxWidth: '125px',
+ width: '100%',
+ },
+ [theme.breakpoints.down('md')]: {
+ '& span': {
+ maxWidth: '90px',
+ marginRight: '20px',
+ },
+ },
+ [theme.breakpoints.down(450)]: {
+ '& span': {
+ maxWidth: '70px',
+ },
+ },
+});
+
+const innerWrapper = theme => ({});
+
+const infoInnerStyle = theme => ({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '16px',
+ paddingTop: '10px',
+});
+
+const userListSx = theme => ({
+ width: '100%',
+ [theme.breakpoints.down('sm')]: {
+ '& li': {
+ marginLeft: '0px!important',
+ },
+ },
+});
+
+const infoStyle = theme => ({
+ display: 'flex',
+ margin: '0 0 50px',
+ flexDirection: 'row',
+ // flexWrap: 'wrap',
+ width: '100%',
+ gap: '40px',
+ rowGap: '15px',
+ '& .tagsWrapper': {
+ width: '100%',
+ },
+ [theme.breakpoints.down('md')]: {
+ gap: '10px',
+ },
+ [theme.breakpoints.down('sm')]: {
+ flexDirection: 'column',
+ gap: '16px',
+ margin: 0,
+ '& .tagsWrapper': {
+ width: '520px',
+ },
+ },
+});
+
+const avatarStyle = theme => ({
+ width: '205px',
+ height: '205px',
+ [theme.breakpoints.down('xs')]: {
+ width: '150px',
+ height: '150px',
+ },
+});
+
+const contentWrapper = theme => ({
+ display: 'flex',
+ gap: '70px',
+ // justifyContent: 'space-between',
+ [theme.breakpoints.down('md')]: {
+ gap: '50px',
+ },
+ [theme.breakpoints.down('sm')]: {
+ flexDirection: 'column',
+ alignItems: 'center',
+ gap: '40px',
+ },
+});
+
+const buttonSx = theme => ({
+ margin: '0 auto',
+ display: 'block',
+ color: theme.palette.background.default,
+ textTransform: 'capitalize',
+ fontWeight: 600,
+ fontSize: '18px',
+ // padding: '9px 50px',
+ width: '234px',
+ borderRadius: '10px',
+ [theme.breakpoints.down('xs')]: {
+ padding: '9px 10px',
+ },
+});
+
+const submitAuditor = theme => ({
+ backgroundColor: theme.palette.secondary.main,
+ '&:hover': {
+ backgroundColor: '#450e5d',
+ },
+});
+
+const infoWrapper = theme => ({
+ display: 'flex',
+ alignItems: 'center',
+ fontWeight: 500,
+ color: '#434242',
+ '& p': {
+ fontSize: 'inherit',
+ maxWidth: '250px',
+ },
+ '& span': {
+ width: '125px',
+ marginRight: '50px',
+ color: '#B2B3B3',
+ },
+ fontSize: '15px',
+ [theme.breakpoints.down('md')]: {
+ '& span': {
+ width: '90px',
+ marginRight: '20px',
+ },
+ '& p': {
+ maxWidth: '190px',
+ },
+ },
+ [theme.breakpoints.down('sm')]: {
+ '& p': {
+ maxWidth: '240px',
+ },
+ },
+ [theme.breakpoints.down('xs')]: {
+ fontSize: '12px',
+ },
+ [theme.breakpoints.down(450)]: {
+ '& span': {
+ width: '70px',
+ marginRight: '20px',
+ },
+ '& p': {
+ maxWidth: '180px',
+ },
+ },
+});
+
+const accountLink = {
+ height: '30px',
+ display: 'flex',
+ alignItems: 'center',
+ color: 'black',
+ textDecoration: 'none',
+};
diff --git a/src/components/OrganizationCard.jsx b/src/components/OrganizationCard.jsx
new file mode 100644
index 00000000..cdbcc196
--- /dev/null
+++ b/src/components/OrganizationCard.jsx
@@ -0,0 +1,116 @@
+import React from 'react';
+import { CUSTOMER } from '../redux/actions/types.js';
+import theme from '../styles/themes.js';
+import { Avatar, Box, Button, Typography } from '@mui/material';
+import { ASSET_URL } from '../services/urls.js';
+import PeopleIcon from '@mui/icons-material/People.js';
+import { useSelector } from 'react-redux';
+import { useNavigate } from 'react-router-dom/dist';
+
+const OrganizationCard = ({ org }) => {
+ const role = useSelector(s => s.user.user.current_role);
+ const navigate = useNavigate();
+
+ return (
+
+
+
+
+ {org.name}
+
+
+
+ {org.members.length}
+
+ {org.organization_type}
+
+
+ navigate(`/o/${org.link_id}`)}
+ sx={buttonSx}
+ variant={'contained'}
+ color={role === CUSTOMER ? 'primary' : 'secondary'}
+ >
+ View more
+
+
+ );
+};
+
+export default OrganizationCard;
+
+const wrapper = (theme, role) => ({
+ backgroundColor: '#fff',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ padding: '20px',
+ gap: '15px',
+ borderRadius: '1.5rem',
+ border: `1px solid ${
+ role === CUSTOMER
+ ? theme.palette.primary.main
+ : theme.palette.secondary.main
+ }!important`,
+ [theme.breakpoints.down('sm')]: {
+ padding: '15px 10px',
+ },
+});
+
+const avatarSx = theme => ({
+ height: '200px',
+ width: '200px',
+ [theme.breakpoints.down('md')]: {
+ width: '150px',
+ height: '150px',
+ },
+ [theme.breakpoints.down('xs')]: {
+ width: '100px',
+ height: '100px',
+ },
+});
+
+const infoWrapper = theme => ({
+ mt: '10px',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ [theme.breakpoints.down('md')]: {
+ '& p': {
+ fontSize: '16px',
+ },
+ },
+ [theme.breakpoints.down('sm')]: {
+ '& p': {
+ fontSize: '14px',
+ },
+ },
+});
+
+const buttonSx = theme => ({
+ width: '100%',
+ borderRadius: '8px',
+ textTransform: 'unset',
+ [theme.breakpoints.down('md')]: {
+ fontSize: '16px',
+ },
+ [theme.breakpoints.down('sm')]: {
+ fontSize: '14px',
+ },
+});
+
+const iconSx = theme => ({
+ [theme.breakpoints.down('md')]: {
+ width: '18px',
+ height: '18px',
+ },
+});
diff --git a/src/components/OrganizationList/OrganizationList.jsx b/src/components/OrganizationList/OrganizationList.jsx
new file mode 100644
index 00000000..8a34f828
--- /dev/null
+++ b/src/components/OrganizationList/OrganizationList.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import { Link, useLocation } from 'react-router-dom';
+import { Avatar, Box, Tooltip } from '@mui/material';
+import { ASSET_URL } from '../../services/urls.js';
+import { CUSTOMER } from '../../redux/actions/types.js';
+import theme from '../../styles/themes.js';
+import { useSelector } from 'react-redux';
+
+const OrganizationList = ({ organizations }) => {
+ const user = useSelector(s => s.user.user);
+ const location = useLocation();
+
+ return (
+
+ {organizations?.map(org => {
+ if (org.avatar) {
+ return (
+
+
+
+
+
+ );
+ } else {
+ return (
+
+
+
+ {org.name}
+
+
+
+ );
+ }
+ })}
+
+ );
+};
+
+export default OrganizationList;
diff --git a/src/components/Project-card.jsx b/src/components/Project-card.jsx
index 3a446b3b..d94eaf17 100644
--- a/src/components/Project-card.jsx
+++ b/src/components/Project-card.jsx
@@ -232,6 +232,22 @@ const ProjectCard = ({ type, project, currentRole, isPublic }) => {
label="Publish"
/>
)}
+ {!isPublic && project?.auditor_organization?.id &&
+ project?.status.toLowerCase() !== RESOLVED.toLowerCase() &&
+
+ Organization
+
+ {project?.auditor_organization?.name}
+
+
+ }
{type !== AUDITOR && (
{
export default ProjectCard;
+const priceTextStyle = {
+ fontWeight: '500',
+ fontSize: '14px!important',
+ [theme.breakpoints.down('sm')]: {
+ fontSize: '11.5px!important',
+ },
+};
+
+const auditNameStyle = {
+ height: '55px',
+ overflow: 'hidden',
+ wordBreak: 'break-word',
+ '-webkit-line-clamp': '2',
+ '-webkit-box-orient': 'vertical',
+ 'text-overflow': 'ellipsis',
+ display: '-webkit-box',
+ fontWeight: '500',
+ fontSize: '18px!important',
+ [theme.breakpoints.down('sm')]: {
+ fontSize: '14px!important',
+ height: '45px',
+ textAlign: 'center',
+ },
+};
+
export const userButtonSx = {
textTransform: 'unset',
display: 'flex',
diff --git a/src/components/Projects.jsx b/src/components/Projects.jsx
index fce92086..a5473fd6 100644
--- a/src/components/Projects.jsx
+++ b/src/components/Projects.jsx
@@ -17,9 +17,11 @@ const Projects = ({ role }) => {
const customer = useSelector(state => state.customer.customer);
const dispatch = useDispatch();
const [error, setError] = useState(null);
+ const organizationAudits = useSelector(state => state.audits.organizationAudits);
+
const projectReducer = useMemo(() => {
if (role === AUDITOR) {
- return projects;
+ return [...projects, ...organizationAudits];
} else {
return (
myProjects &&
diff --git a/src/components/User-info.jsx b/src/components/User-info.jsx
index 83f381a3..a014f4ec 100644
--- a/src/components/User-info.jsx
+++ b/src/components/User-info.jsx
@@ -4,13 +4,12 @@ import {
Box,
Button,
Typography,
- Link,
useMediaQuery,
Tooltip,
} from '@mui/material';
import GitHubIcon from '@mui/icons-material/GitHub.js';
import theme from '../styles/themes.js';
-import { useNavigate, useSearchParams } from 'react-router-dom/dist';
+import { useNavigate, Link, useSearchParams } from 'react-router-dom/dist';
import { useDispatch, useSelector } from 'react-redux';
import Loader from './Loader.jsx';
import { AUDITOR, CUSTOMER } from '../redux/actions/types.js';
@@ -26,18 +25,21 @@ import WalletConnectIcon from './icons/WalletConnectIcon.jsx';
import { clearUserMessages } from '../redux/actions/userAction.js';
import CustomSnackbar from './custom/CustomSnackbar.jsx';
import Headings from '../router/Headings.jsx';
+import OrganizationList from './OrganizationList/OrganizationList.jsx';
import Star from './icons/Star.jsx';
import { getAuditorRating } from '../redux/actions/auditorAction.js';
import RatingDetails from './RatingDetails.jsx';
import UserFeedbacks from './UserFeedbacks.jsx';
import ArrowBackIcon from '@mui/icons-material/ArrowBack.js';
+import { useLocation } from 'react-router-dom';
const UserInfo = ({ role, linkId }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
-
const matchXs = useMediaQuery(theme.breakpoints.down('xs'));
- const matchXxs = useMediaQuery(theme.breakpoints.down(590));
+ const matchXxs = useMediaQuery(theme.breakpoints.down(850));
+ const organizations = useSelector(s => s.organization.organizations);
+ const invites = useSelector(s => s.organization.invites);
const [searchParams, setSearchParams] = useSearchParams();
const [isDetailsOpen, setIsDetailsOpen] = useState(
@@ -159,6 +161,37 @@ const UserInfo = ({ role, linkId }) => {
)}
+ {(!!organizations.length || !!invites.length) && (
+
+ {!!invites.length && (
+ <>
+ Invites
+
+ >
+ )}
+ {!!organizations.length && (
+ <>
+
+ Organization
+
+
+ >
+ )}
+
+ )}
{isDetailsOpen ? (
@@ -327,6 +360,13 @@ const UserInfo = ({ role, linkId }) => {
Edit
+ navigate('/create-organization')}
+ sx={buttonSx}
+ variant={'contained'}
+ >
+ Create organization
+
@@ -448,7 +488,8 @@ const buttonSx = theme => ({
textTransform: 'capitalize',
fontWeight: 600,
fontSize: '18px',
- width: '214px',
+ // padding: '9px 50px',
+ width: '234px',
borderRadius: '10px',
[theme.breakpoints.down('xs')]: {
padding: '9px 10px',
@@ -505,6 +546,14 @@ const infoWrapper = theme => ({
},
});
+const accountLink = {
+ height: '30px',
+ display: 'flex',
+ alignItems: 'center',
+ color: 'black',
+ textDecoration: 'none',
+};
+
const ratingButton = {
color: 'black',
mt: '20px',
diff --git a/src/components/UserListItem/UserLIstItem.jsx b/src/components/UserListItem/UserLIstItem.jsx
new file mode 100644
index 00000000..0379c162
--- /dev/null
+++ b/src/components/UserListItem/UserLIstItem.jsx
@@ -0,0 +1,303 @@
+import React, { useEffect, useState } from 'react';
+import {
+ Avatar,
+ Box,
+ Button,
+ Checkbox,
+ Chip,
+ FormControlLabel,
+ ListItem,
+ ListItemAvatar,
+ ListItemText,
+ Modal,
+ Typography,
+} from '@mui/material';
+import EditIcon from '@mui/icons-material/Edit.js';
+import {
+ addUserInOrganization,
+ changeAccessLevel,
+ deleteUserFromOrganization,
+} from '../../redux/actions/organizationAction.js';
+import DeleteForeverRoundedIcon from '@mui/icons-material/DeleteForeverRounded.js';
+import ListItemButton from '@mui/material/ListItemButton';
+import { ASSET_URL } from '../../services/urls.js';
+import { useDispatch, useSelector } from 'react-redux';
+import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined';
+import IconButton from '@mui/material/IconButton';
+import { addTestsLabel } from '../../lib/helper.js';
+import { ArrowBack } from '@mui/icons-material';
+import Radio from '@mui/material/Radio';
+import { CUSTOMER } from '../../redux/actions/types.js';
+import theme from '../../styles/themes.js';
+
+const UserLIstItem = ({ value, labelId, organization }) => {
+ const user = useSelector(s => s.user.user);
+ const [isOpen, setIsOpen] = useState(false);
+ const dispatch = useDispatch();
+ const [rulesOfMember, setRulesOfMember] = useState('Representative');
+
+ const handleClose = () => {
+ setRulesOfMember(value.access_level);
+ setIsOpen(false);
+ };
+
+ const handleChangeAccess = () => {
+ const data = [
+ {
+ user_id: value?.user_id,
+ access_level: rulesOfMember,
+ },
+ ];
+ dispatch(
+ changeAccessLevel(
+ organization.id,
+ value.user_id,
+ { access_level: rulesOfMember },
+ organization.link_id,
+ ),
+ );
+ handleClose();
+ };
+
+ useEffect(() => {
+ setRulesOfMember(value.access_level || 'Representative');
+ }, [value.access_level]);
+
+ return (
+
+ {
+ setIsOpen(true);
+ }}
+ >
+
+
+ {
+ dispatch(
+ deleteUserFromOrganization(
+ organization.id,
+ value.user_id,
+ organization.link_id,
+ ),
+ );
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+ Current organization
+
+
+
+ {organization.name}
+
+
+ {`Change the role of ${value.username}`}
+
+
+
+
+ setRulesOfMember('Owner')}
+ />
+ }
+ sx={{ marginX: '0' }}
+ label="Owner"
+ labelPlacement="right"
+ />
+
+ Has full control over organization management.
+
+
+
+ setRulesOfMember('Editor')}
+ />
+ }
+ label="Editor"
+ sx={{ marginX: '0' }}
+ labelPlacement="right"
+ />
+
+ Can manage audits and communicate on behalf of the
+ organization.
+
+
+
+ setRulesOfMember('Representative')}
+ />
+ }
+ label="Representative"
+ sx={{ marginX: '0' }}
+ labelPlacement="right"
+ />
+
+ Can communicate on behalf of the organization but cannot
+ manage audits.
+
+
+
+
+ Save
+
+
+
+
+
+ )
+ }
+ disablePadding
+ >
+
+
+
+
+
+ {value.access_level.includes('Owner') && (
+
+ )}
+
+
+ );
+};
+
+export default UserLIstItem;
+
+const roleDescriptionTitle = theme => ({
+ fontSize: '16px',
+ color: '#9f9f9f',
+ marginLeft: '42px',
+ [theme.breakpoints.down('md')]: {
+ fontSize: '12px',
+ },
+ [theme.breakpoints.down('xs')]: {
+ fontSize: '10px',
+ },
+});
+
+const modalSx = theme => ({
+ position: 'absolute',
+ bgcolor: 'background.paper',
+ borderRadius: '8px',
+ boxShadow: 24,
+ p: 3,
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ backgroundColor: 'white',
+ padding: '10px',
+ width: '700px',
+ [theme.breakpoints.down('sm')]: {
+ paddingBottom: '30px',
+ height: '100%',
+ width: '100%',
+ },
+ [theme.breakpoints.down(500)]: {
+ width: '310px',
+ p: 2,
+ },
+});
+
+const userActionSx = theme => ({
+ minWidth: 'unset',
+ padding: '5px',
+ mx: '5px',
+});
diff --git a/src/components/audit-request-info.jsx b/src/components/audit-request-info.jsx
index 66a471ff..69c6bcfd 100644
--- a/src/components/audit-request-info.jsx
+++ b/src/components/audit-request-info.jsx
@@ -13,6 +13,12 @@ import {
Modal,
Tooltip,
Collapse,
+ Popover,
+ Avatar,
+ ListItemAvatar,
+ ListItem,
+ ListItemText,
+ List,
} from '@mui/material';
import { CustomCard } from './custom/Card.jsx';
import theme from '../styles/themes.js';
@@ -39,6 +45,11 @@ import EditTags from './EditDescription/EditTags.jsx';
import EditPrice from './EditDescription/EditPrice.jsx';
import ExpandLessOutlinedIcon from '@mui/icons-material/ExpandLessOutlined.js';
import EditIcon from '@mui/icons-material/Edit.js';
+import Star from './icons/Star.jsx';
+import Currency from './icons/Currency.jsx';
+import { ASSET_URL } from '../services/urls.js';
+import ListItemButton from '@mui/material/ListItemButton';
+import TypeChat from './Chat/TypeChat.jsx';
const AuditRequestInfo = ({
project = null,
@@ -55,20 +66,55 @@ const AuditRequestInfo = ({
const navigate = useNavigate();
const [open, setOpen] = useState(false);
+ const [anchorEl, setAnchorEl] = useState(null);
const [confirmDeclineOpen, setConfirmDeclineOpen] = useState(false);
const [showAcceptButton, setShowAcceptButton] = useState(true);
const [showFullHeader, setShowFullHeader] = useState(false);
+ const [visible, setVisible] = useState(false);
+ const organizations = useSelector(state => state.organization.organizations);
const { auditor } = useSelector(s => s.auditor);
- const { auditRequest, auditRequests, successMessage } = useSelector(
+ const [auditorData, setAuditorData] = useState({});
+ const { auditRequest, auditRequests, successMessage, organizationAuditRequests } = useSelector(
s => s.audits,
);
const { user } = useSelector(s => s.user);
const { chatList } = useSelector(s => s.chat);
const [showFull, setShowFull] = useState(false);
- const handleOpen = () => {
+ const handleClick = event => {
+ setAnchorEl(event.currentTarget);
+ setVisible(true);
+ };
+
+ const handleCloseAnchor = () => {
+ setAnchorEl(null);
+ };
+
+ const anchorOrigin = {
+ vertical: visible ? 'bottom' : 'top',
+ horizontal: 'left',
+ };
+
+ const transformOrigin = {
+ vertical: visible ? 'top' : 'bottom',
+ horizontal: 'left',
+ };
+
+ const openAnchor = Boolean(anchorEl);
+ const id = openAnchor ? 'simple-popover' : undefined;
+
+ const handleOpen = event => {
if (user.current_role === AUDITOR && isAuth() && auditor?.first_name) {
- setOpen(true);
+ if (!organizations.length) {
+ setAuditorData(auditor);
+ setOpen(true);
+ } else {
+ if (auditRequest?.auditor_organization) {
+ handleChose(auditRequest?.auditor_organization);
+ } else {
+ handleClick(event);
+ }
+ }
} else if (
user.current_role !== AUDITOR &&
isAuth() &&
@@ -76,6 +122,7 @@ const AuditRequestInfo = ({
) {
dispatch(changeRolePublicAuditorNoRedirect(AUDITOR, user.id, auditor));
handleError();
+ setAuditorData(auditor);
setOpen(true);
} else if (
!auditor?.first_name &&
@@ -90,12 +137,18 @@ const AuditRequestInfo = ({
) {
dispatch(changeRolePublicAuditor(AUDITOR, user.id, auditor));
handleError();
+ setAuditorData(auditor);
setOpen(true);
} else {
navigate('/sign-in');
}
};
+ const handleChose = auditor => {
+ setAuditorData(auditor);
+ setOpen(true);
+ };
+ //
const handleClose = () => {
setOpen(false);
};
@@ -140,11 +193,19 @@ const AuditRequestInfo = ({
};
const handleAccept = () => {
+
+
const isRequestFound = auditRequests?.find(
req => req.id === auditRequest.id,
+ ) || organizationAuditRequests?.find(
+ req => req.id === auditRequest.id,
);
if (isRequestFound) {
- dispatch(confirmAudit(auditRequest, true, `/audit/${auditRequest.id}`));
+ if (isRequestFound?.auditor_organization) {
+ dispatch(confirmAudit({ ...isRequestFound, auditor_organization: isRequestFound?.auditor_organization.id }, true, `/audit/${isRequestFound.id}`));
+ } else {
+ dispatch(confirmAudit(isRequestFound, true, `/audit/${isRequestFound.id}`));
+ }
}
};
@@ -445,6 +506,66 @@ const AuditRequestInfo = ({
>
Make offer
+
+
+ handleChose(auditor)}>
+
+
+
+
+
+
+
+ {organizations.map(org => {
+ const member = org.members.find(
+ member => member.user_id === user.id,
+ );
+ const hasEditorAccess =
+ member.access_level === 'Editor' ||
+ member.access_level === 'Owner';
+
+ return (
+ {
+ if (hasEditorAccess) {
+ handleChose(org);
+ }
+ }}
+ >
+
+
+
+
+
+
+
+ );
+ })}
+
+
{showAcceptButton &&
auditRequest &&
!isModal &&
@@ -469,7 +590,7 @@ const AuditRequestInfo = ({
disableScrollLock
>
{
+
- {auditor.first_name} {auditor.last_name}
-
+ {auditor.first_name || auditor.name} {auditor.last_name || ''}
+
+ {auditor.owner && (
+
+ Organization
+
+ )}
+
@@ -70,6 +77,13 @@ const nameStyle = theme => ({
},
});
+const organizationStyle = theme => ({
+ fontWeight: '500',
+ color: '#434242',
+ fontSize: '12px!important',
+ marginTop: '2px',
+});
+
const statusStyle = theme => ({
fontWeight: '500',
color: '#434242',
diff --git a/src/components/custom/customMessage.jsx b/src/components/custom/customMessage.jsx
index 34f2bfd3..9cbc582f 100644
--- a/src/components/custom/customMessage.jsx
+++ b/src/components/custom/customMessage.jsx
@@ -8,6 +8,7 @@ import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import { readMessage } from '../../redux/actions/websocketAction.js';
import { useNavigate } from 'react-router-dom/dist';
import NotificationMessage from '../NotificationMessage.jsx';
+import { getOrganizationById } from '../../redux/actions/organizationAction.js';
const CustomMessage = ({ message }) => {
const role = useSelector(s => s.user.user.current_role);
@@ -116,6 +117,11 @@ const CustomMessage = ({ message }) => {
{
+ if (message.inner.links[0].includes('/o/')) {
+ dispatch(
+ getOrganizationById(message.inner.links[0].split('/')[2]),
+ );
+ }
navigate(message.inner.links[0]);
handleClose(message.id);
}}
diff --git a/src/components/custom/info-card.jsx b/src/components/custom/info-card.jsx
index adf07731..7f3ad75c 100644
--- a/src/components/custom/info-card.jsx
+++ b/src/components/custom/info-card.jsx
@@ -1,9 +1,11 @@
import React from 'react';
import { Box } from '@mui/material';
-
-const InfoCard = ({ children, role = 'customer' }) => {
+import theme from '../../styles/themes.js';
+const InfoCard = ({ children, role = 'customer', sx }) => {
return (
-
+
{children}
);
diff --git a/src/components/forms/Avatar-form/index.jsx b/src/components/forms/Avatar-form/index.jsx
index 51c51539..a0c24b01 100644
--- a/src/components/forms/Avatar-form/index.jsx
+++ b/src/components/forms/Avatar-form/index.jsx
@@ -24,6 +24,21 @@ const AvatarForm = ({
const [avatarField, , fieldHelper] = useField(name);
const [avatarPreview, setAvatarPreview] = useState(null);
+ const [avatarFile, setAvatarFile] = useState(null);
+
+ useEffect(() => {
+ Array.from(formData.keys()).forEach(key => {
+ formData.delete(key);
+ });
+
+ if (avatarFile) {
+ formData.set('file', avatarFile);
+ formData.set('private', 'false');
+ formData.set('file_entity', AVATAR_ENTITY);
+ formData.set('parent_entity_id', user.id);
+ formData.set('parent_entity_source', user.current_role);
+ }
+ }, [avatarFile, formData, user.id, user.current_role]);
const handleUpdateAvatar = e => {
const file = e.target.files[0];
@@ -32,11 +47,7 @@ const AvatarForm = ({
if (fileSize > 10_000_000) {
return setError('File size is too big');
} else {
- formData.set('file', file);
- formData.set('private', 'false');
- formData.set('file_entity', AVATAR_ENTITY);
- formData.set('parent_entity_id', user.id);
- formData.set('parent_entity_source', user.current_role);
+ setAvatarFile(file);
const reader = new FileReader();
reader.onloadend = () => {
@@ -49,6 +60,17 @@ const AvatarForm = ({
}
};
+ const deletePhoto = () => {
+ if (avatarField?.value) {
+ setDeletedAvatar(avatarField.value);
+ }
+ if (avatarPreview) {
+ setAvatarFile(null);
+ setAvatarPreview(null);
+ }
+ fieldHelper.setValue('');
+ };
+
useEffect(() => {
if (user.is_new && !avatarField.value) {
const avatarLink = user?.linked_accounts?.[0]?.avatar;
@@ -57,34 +79,14 @@ const AvatarForm = ({
if (isThirdPartyImage) {
axios.get(avatarLink, { responseType: 'blob' }).then(({ data }) => {
const filename = user.id + data.type.replace(/image\//, '.');
- formData.append('file', data);
- formData.append('private', 'false');
+ setAvatarFile(data);
formData.append('original_name', filename);
- formData.append('file_entity', AVATAR_ENTITY);
- formData.append('parent_entity_id', user.id);
- formData.append('parent_entity_source', user.current_role);
sendAvatar(true);
});
}
}
}, [user]);
- const deletePhoto = () => {
- if (avatarField?.value) {
- setDeletedAvatar(avatarField.value);
- }
- if (avatarPreview) {
- formData.delete('file');
- formData.delete('private');
- formData.delete('original_name');
- formData.delete('file_entity');
- formData.delete('parent_entity_id');
- formData.delete('parent_entity_source');
- setAvatarPreview(null);
- }
- fieldHelper.setValue('');
- };
-
return (
<>
{avatarPreview ? (
@@ -131,7 +133,6 @@ const AvatarForm = ({
};
export default AvatarForm;
-
const avatarSx = theme => ({
mb: '20px',
[theme.breakpoints.down('sm')]: {
diff --git a/src/components/forms/change-link-id/index.jsx b/src/components/forms/change-link-id/index.jsx
index a63d8e6c..a77e55d6 100644
--- a/src/components/forms/change-link-id/index.jsx
+++ b/src/components/forms/change-link-id/index.jsx
@@ -20,8 +20,9 @@ import SimpleField from '../fields/simple-field.jsx';
import theme from '../../../styles/themes.js';
import { updateAuditor } from '../../../redux/actions/auditorAction.js';
import { updateCustomer } from '../../../redux/actions/customerAction.js';
+import { updateOrganization } from '../../../redux/actions/organizationAction.js';
-const ChangeLinkId = ({ setNewLinkId }) => {
+const ChangeLinkId = ({ setNewLinkId, org }) => {
const dispatch = useDispatch();
const matchXs = useMediaQuery(theme.breakpoints.down('xs'));
@@ -84,12 +85,18 @@ const ChangeLinkId = ({ setNewLinkId }) => {
validationSchema={validationSchema}
onSubmit={values => {
setNewLinkId(values.link_id);
- if (user.current_role === AUDITOR) {
- dispatch(updateAuditor(values));
- } else if (
- user?.current_role?.toLowerCase() === CUSTOMER?.toLowerCase()
- ) {
- dispatch(updateCustomer(values));
+ if (!org) {
+ if (user.current_role === AUDITOR) {
+ dispatch(updateAuditor(values));
+ } else if (
+ user?.current_role?.toLowerCase() === CUSTOMER?.toLowerCase()
+ ) {
+ dispatch(updateCustomer(values));
+ }
+ } else {
+ dispatch(
+ updateOrganization({ ...values, id: org.id }, values.link_id),
+ );
}
}}
>
diff --git a/src/components/forms/create-edit-organization-form/CreateEditOrganizationForm.jsx b/src/components/forms/create-edit-organization-form/CreateEditOrganizationForm.jsx
new file mode 100644
index 00000000..46706a34
--- /dev/null
+++ b/src/components/forms/create-edit-organization-form/CreateEditOrganizationForm.jsx
@@ -0,0 +1,533 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { Field, Form, Formik } from 'formik';
+import * as Yup from 'yup';
+import {
+ Box,
+ Button,
+ Checkbox,
+ Typography,
+ useMediaQuery,
+} from '@mui/material';
+import SimpleField from '../fields/simple-field.jsx';
+import theme from '../../../styles/themes.js';
+import Loader from '../../Loader.jsx';
+import { AUDITOR, CUSTOMER } from '../../../redux/actions/types.js';
+import AvatarForm from '../Avatar-form/index.jsx';
+import { SliderRange } from '../salary-slider/slider-range.jsx';
+import { addTestsLabel } from '../../../lib/helper.js';
+import { useNavigate } from 'react-router-dom/dist';
+import ArrowBackIcon from '@mui/icons-material/ArrowBack';
+import { history } from '../../../services/history.js';
+import {
+ createOrganization,
+ updateOrganization,
+} from '../../../redux/actions/organizationAction.js';
+import Radio from '@mui/material/Radio';
+import RadioGroup from '@mui/material/RadioGroup';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import FormControl from '@mui/material/FormControl';
+import FormLabel from '@mui/material/FormLabel';
+import {
+ changeRoleCreateOrganization,
+ changeRolePublicAuditorNoRedirect,
+} from '../../../redux/actions/userAction.js';
+import axios from 'axios';
+import Cookies from 'js-cookie';
+import { ASSET_URL } from '../../../services/urls.js';
+
+const GoBack = ({ role, newLinkId }) => {
+ const navigate = useNavigate();
+ const handleGoBack = () => {
+ if (newLinkId) {
+ navigate(`/${role[0]}/${newLinkId}`);
+ } else {
+ navigate(-1);
+ }
+ };
+ return (
+
+
+
+ );
+};
+
+const CreateEditOrganizationForm = ({
+ role,
+ needLoad,
+ organization,
+ newLinkId,
+}) => {
+ const matchSm = useMediaQuery(theme.breakpoints.down('sm'));
+ const matchXs = useMediaQuery(theme.breakpoints.down('xs'));
+ const dispatch = useDispatch();
+ const { user } = useSelector(s => s.user);
+ const { customer } = useSelector(s => s.customer);
+ const { auditor } = useSelector(s => s.auditor);
+ const navigate = useNavigate();
+ const [isDirty, setIsDirty] = useState(false);
+ const [deletedAvatar, setDeletedAvatar] = useState(null);
+ const formData = new FormData();
+
+ const sendAvatar = async (withSave = false) => {
+ if (formData.get('file')) {
+
+ try {
+
+ const { data } = await axios.post(ASSET_URL, formData, {
+ headers: { Authorization: 'Bearer ' + Cookies.get('token') },
+ });
+
+ const avatar = data.id;
+ if (withSave) {
+ if (role === AUDITOR) {
+ dispatch(updateAuditor({ avatar }, false));
+ } else {
+ dispatch(updateCustomer({ avatar }, false));
+ }
+ }
+ return avatar;
+ } catch (err) {
+ setError('Error while uploading file');
+ console.error(err);
+ console.log(err, 'err');
+ } finally {
+ formData.delete('file');
+ formData.delete('private');
+ formData.delete('original_name');
+ formData.delete('file_entity');
+ formData.delete('parent_entity_id');
+ formData.delete('parent_entity_source');
+ }
+ }
+ return null;
+ };
+
+ if (!organization.id && needLoad) {
+ return (
+
+
+
+ );
+ } else {
+ return (
+ {
+ const avatar = await sendAvatar();
+
+ setSubmitting(true);
+ setIsDirty(false);
+
+ if (deletedAvatar) {
+ try {
+ await axios.delete(`${ASSET_URL}/id/${deletedAvatar}`, {
+ headers: { Authorization: 'Bearer ' + Cookies.get('token') },
+ });
+ } catch (e) {}
+ }
+
+ if (avatar) {
+ values.avatar = avatar;
+ }
+
+ if (!values.id) {
+ if (
+ values.organization_type?.toLowerCase() !==
+ user.current_role?.toLowerCase()
+ ) {
+ dispatch(
+ changeRoleCreateOrganization(
+ user.current_role?.toLowerCase() !== AUDITOR.toLowerCase()
+ ? AUDITOR
+ : CUSTOMER,
+ user.id,
+ values,
+ `/${user.current_role[0]}/${user.id}`,
+ ),
+ );
+ } else {
+ dispatch(
+ createOrganization(
+ values,
+ `/${user.current_role[0]}/${user.id}`,
+ ),
+ );
+ }
+ } else {
+ dispatch(
+ updateOrganization(values, `/${user.current_role[0]}/${user.id}`),
+ );
+ }
+ resetForm();
+ }}
+ >
+ {({ handleSubmit, values, setFieldValue, dirty }) => {
+ useEffect(() => {
+ setIsDirty(dirty);
+ }, [dirty]);
+ useEffect(() => {
+ const unblock = history.block(({ location }) => {
+ if (!isDirty) {
+ unblock();
+ return navigate(location);
+ }
+
+ const confirmed = window.confirm(
+ 'Do you want to save changes before leaving the page?',
+ );
+
+ if (confirmed) {
+ handleSubmit(values);
+ unblock();
+ return navigate(location);
+ } else {
+ unblock();
+ return navigate(location);
+ }
+ });
+
+ if (!isDirty) {
+ unblock();
+ }
+
+ return () => {
+ unblock();
+ };
+ }, [history, isDirty]);
+
+ return (
+
+ );
+ }}
+
+ );
+ }
+};
+
+export default CreateEditOrganizationForm;
+
+const EditOrganizationSchema = Yup.object().shape({
+ name: Yup.string().required('required'),
+ // contacts: Yup.object().shape({
+ // email: Yup.string().email('Invalid email').required('required'),
+ // telegram: Yup.string(),
+ // }),
+});
+
+const alertDescSx = theme => ({
+ fontSize: '16px',
+ [theme.breakpoints.down('md')]: {
+ fontSize: '12px',
+ },
+});
+
+const backBtnSx = theme => ({
+ position: 'absolute',
+ left: '-70px',
+ top: '-30px',
+ [theme.breakpoints.down('sm')]: {
+ left: '-50px',
+ },
+ [theme.breakpoints.down('xs')]: {
+ left: '-40px',
+ },
+});
+
+const wrapper = theme => ({
+ display: 'flex',
+ position: 'relative',
+ gap: '52px',
+ [theme.breakpoints.down('sm')]: {
+ flexDirection: 'column',
+ gap: '12px',
+ },
+});
+
+const rateLabel = theme => ({
+ fontSize: '15px',
+ color: '#B2B3B3',
+ fontWeight: 500,
+});
+
+const fieldsWrapper = theme => ({
+ display: 'flex',
+ gap: '52px',
+ width: '100%',
+ '& p': {
+ color: '#B2B3B3',
+ },
+ [theme.breakpoints.down('sm')]: {
+ flexDirection: 'column',
+ gap: '14px',
+ },
+});
+
+const fieldWrapper = theme => ({
+ width: '100%',
+ gap: '18px',
+ display: 'flex',
+ flexDirection: 'column',
+ '& .tags-array-wrapper': {
+ margin: 'auto 0 0 0',
+ },
+ '& .password-wrapper': {
+ gap: '12px',
+ },
+ '& .field-wrapper': {
+ gap: '12px',
+ },
+ [theme.breakpoints.down('sm')]: {
+ gap: '14px',
+ '& .field-wrapper': {
+ gap: '8px',
+ },
+ '& .password-wrapper': {
+ gap: '8px',
+ },
+ },
+ [theme.breakpoints.down('xs')]: {
+ '& p': {
+ fontSize: '12px',
+ },
+ '& input': {
+ fontSize: '12px',
+ },
+ },
+});
+
+const buttonSx = {
+ display: 'block',
+ margin: '70px auto 0',
+ textTransform: 'unset',
+ padding: '13px 0',
+ fontWeight: 600,
+ fontSize: '14px',
+ lineHeight: 1.2,
+ width: '200px',
+ borderRadius: '10px',
+};
+
+const avatarWrapper = theme => ({
+ '& button': {
+ textTransform: 'unset',
+ '& svg': { marginRight: '5px' },
+ },
+ '& .MuiAvatar-root': {
+ width: '205px',
+ height: '205px',
+ },
+ [theme.breakpoints.down('md')]: {
+ '& .MuiAvatar-root': {
+ width: '158px',
+ height: '158px',
+ },
+ '& button': { fontSize: '12px' },
+ },
+ [theme.breakpoints.down('sm')]: {
+ '& .MuiAvatar-root': {
+ width: '128px',
+ height: '128px',
+ },
+ display: 'flex',
+ gap: '22px',
+ '& p': { color: '#B2B3B3' },
+ },
+ [theme.breakpoints.down('xs')]: {
+ '& .MuiAvatar-root': {
+ width: '80px',
+ height: '80px',
+ },
+ '& button': {
+ paddingX: '4px',
+ fontSize: '10px',
+ '& svg': { marginRight: 0 },
+ },
+ },
+});
diff --git a/src/components/forms/edit-profile-form/edit-profile-form.jsx b/src/components/forms/edit-profile-form/edit-profile-form.jsx
index fbe0b986..f17e716b 100644
--- a/src/components/forms/edit-profile-form/edit-profile-form.jsx
+++ b/src/components/forms/edit-profile-form/edit-profile-form.jsx
@@ -82,6 +82,7 @@ const EditProfileForm = ({ role, newLinkId }) => {
};
const sendAvatar = async (withSave = false) => {
+
if (formData.get('file')) {
try {
const { data } = await axios.post(ASSET_URL, formData, {
diff --git a/src/components/header/UserMenu.jsx b/src/components/header/UserMenu.jsx
index 7a01e056..3613e398 100644
--- a/src/components/header/UserMenu.jsx
+++ b/src/components/header/UserMenu.jsx
@@ -26,6 +26,7 @@ export const UserMenu = ({ open, handleClose, anchor, userAvatar, pages }) => {
const { customer } = useSelector(s => s.customer);
const navigate = useNavigate();
const matchSm = useMediaQuery(theme.breakpoints.down(1080));
+ const organizations = useSelector(s => s.organization.organizations);
const currentRole = useSelector(s => s.user.user.current_role);
const { differentRoleUnreadMessages } = useSelector(s => s.chat);
@@ -49,6 +50,10 @@ export const UserMenu = ({ open, handleClose, anchor, userAvatar, pages }) => {
navigate(`/${rolePrefix}/${link_id}`);
};
+ const handleMyOrganizationClick = () => {
+ navigate(`/my-organizations`);
+ };
+
return (
+
+ {/*{!!organizations.length && (*/}
+
+ {/*)}*/}
{matchSm &&
pages?.map(el =>
el.menuOptions
diff --git a/src/components/homepage/auditors-projects-section/PublicProjectCard.jsx b/src/components/homepage/auditors-projects-section/PublicProjectCard.jsx
index 1ae06ab3..613ea40e 100644
--- a/src/components/homepage/auditors-projects-section/PublicProjectCard.jsx
+++ b/src/components/homepage/auditors-projects-section/PublicProjectCard.jsx
@@ -14,6 +14,8 @@ import React, { useState } from 'react';
import AuditRequestInfo from '../../audit-request-info.jsx';
import CustomSnackbar from '../../custom/CustomSnackbar.jsx';
import { addTestsLabel } from '../../../lib/helper.js';
+import { useDispatch, useSelector } from 'react-redux';
+import { clearMessage } from '../../../redux/actions/auditAction.js';
const PublicProjectCard = ({ project }) => {
const navigate = useNavigate();
@@ -23,7 +25,10 @@ const PublicProjectCard = ({ project }) => {
const handleView = e => {
setOpenModal(true);
};
-
+ const dispatch = useDispatch();
+ const { auditRequest, auditRequests, successMessage } = useSelector(
+ s => s.audits,
+ );
const handleCloseModal = () => {
setOpenModal(false);
};
@@ -58,13 +63,14 @@ const PublicProjectCard = ({ project }) => {
{
+ dispatch(clearMessage());
setMessage(null);
setErrorMessage(null);
}}
severity={errorMessage ? 'error' : 'success'}
- text={message || errorMessage}
+ text={message || errorMessage || successMessage}
/>
diff --git a/src/components/modal/OfferModal.jsx b/src/components/modal/OfferModal.jsx
index ded816ff..6828e71b 100644
--- a/src/components/modal/OfferModal.jsx
+++ b/src/components/modal/OfferModal.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { useDispatch } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { Field, Form, Formik } from 'formik';
import * as Yup from 'yup';
import dayjs from 'dayjs';
@@ -28,10 +28,12 @@ const OfferModal = ({
stayHere,
}) => {
const dispatch = useDispatch();
+ const auditorReducer = useSelector(s => s.auditor.auditor);
const initialValues = {
- auditor_id: auditor?.user_id,
- auditor_contacts: { ...auditor?.contacts },
+ auditor_organization: auditor?.id,
+ auditor_id: auditorReducer?.user_id,
+ auditor_contacts: { ...auditorReducer?.contacts },
customer_contacts: { ...project?.creator_contacts },
customer_id: project?.customer_id,
last_changer: user.current_role,
@@ -50,7 +52,7 @@ const OfferModal = ({
scope: project?.scope ||
project?.project_scope || { type: SCOPE_LINKS, content: [] },
time_frame: '',
- };
+ }
return (
@@ -68,7 +70,6 @@ const OfferModal = ({
onSubmit={values => {
const newValue = {
...values,
- auditor_id: auditor?.user_id,
auditor_contacts: { ...auditor?.contacts },
price: parseInt(values.price),
total_cost: parseInt(values.total_cost),
diff --git a/src/pages/AuditorsPage.jsx b/src/pages/AuditorsPage.jsx
index f7b68398..0aeabce6 100644
--- a/src/pages/AuditorsPage.jsx
+++ b/src/pages/AuditorsPage.jsx
@@ -75,6 +75,7 @@ const AuditorsPage = () => {
return { ...data, page };
});
};
+ const previousPath = location.state?.from || '/';
useEffect(() => {
if (query) {
diff --git a/src/pages/ChatPage.jsx b/src/pages/ChatPage.jsx
index 1678fe83..3640ff3d 100644
--- a/src/pages/ChatPage.jsx
+++ b/src/pages/ChatPage.jsx
@@ -1,19 +1,34 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
-import { Box, Button, IconButton, useMediaQuery } from '@mui/material';
+import {
+ Avatar,
+ Box,
+ Button,
+ Collapse,
+ Divider,
+ IconButton,
+ Tooltip,
+ useMediaQuery,
+} from '@mui/material';
import Layout from '../styles/Layout.jsx';
import { CustomCard } from '../components/custom/Card';
import ChatList from '../components/Chat/ChatList.jsx';
import CurrentChat from '../components/Chat/CurrentChat.jsx';
-import { chatSetError, setCurrentChat } from '../redux/actions/chatActions.js';
+import {
+ chatSetError,
+ getChatListByOrg,
+ setCurrentChat,
+} from '../redux/actions/chatActions.js';
import theme from '../styles/themes.js';
import MenuIcon from '@mui/icons-material/Menu.js';
-import { useNavigate } from 'react-router-dom/dist';
+import { useNavigate, useSearchParams } from 'react-router-dom/dist';
import ArrowBackIcon from '@mui/icons-material/ArrowBack.js';
import { AUDITOR, CUSTOMER } from '../redux/actions/types.js';
import Headings from '../router/Headings.jsx';
import CustomSnackbar from '../components/custom/CustomSnackbar.jsx';
+import { ASSET_URL } from '../services/urls.js';
+import KeyboardDoubleArrowLeftIcon from '@mui/icons-material/KeyboardDoubleArrowLeft';
const ChatPage = () => {
const dispatch = useDispatch();
@@ -22,12 +37,15 @@ const ChatPage = () => {
const { id } = useParams();
const matchXs = useMediaQuery(theme.breakpoints.down('xs'));
const [chatListIsOpen, setChatListIsOpen] = useState(matchXs && !id);
- const { chatList, chatMessages, currentChat, error } = useSelector(
- s => s.chat,
- );
+ const { chatList, chatMessages, currentChat, error, orgChatList } =
+ useSelector(s => s.chat);
const { user } = useSelector(s => s.user);
const { auditor } = useSelector(s => s.auditor);
const { customer } = useSelector(s => s.customer);
+ const { organizations } = useSelector(s => s.organization);
+ const [searchParams, setSearchParams] = useSearchParams();
+ const orgId = searchParams.get('org');
+ const xsMatch = useMediaQuery(theme.breakpoints.down('xs'));
useEffect(() => {
if (
@@ -46,21 +64,44 @@ const ChatPage = () => {
}
}, [user, auditor, customer]);
+ const profile = useMemo(() => {
+ if (user.current_role.toLowerCase() === AUDITOR.toLowerCase()) {
+ return auditor;
+ } else {
+ return customer;
+ }
+ }, [user.current_role, auditor, customer]);
+
useEffect(() => {
if (id && !currentChat?.isNew) {
const chat = chatList.find(chat => chat.id === id);
+ const chatOrg = orgChatList?.find(chat => chat.id === id);
const members = chat?.members.map(member => member.id);
+ const orgMembers = chatOrg?.members.map(member => member.id);
const role = chat?.members.find(member => member.id !== user.id)?.role;
-
- if (chat) {
- dispatch(
- setCurrentChat(chat?.id, {
- name: chat?.name,
- avatar: chat?.avatar,
- role,
- members,
- }),
- );
+ const orgRole = chatOrg?.members.find(
+ member => member.id !== user.id,
+ )?.role;
+ if (chat || chatOrg) {
+ if (orgId) {
+ dispatch(
+ setCurrentChat(chatOrg?.id, {
+ name: chatOrg?.name,
+ avatar: chatOrg?.avatar,
+ orgRole,
+ orgMembers,
+ }),
+ );
+ } else {
+ dispatch(
+ setCurrentChat(chat?.id, {
+ name: chat?.name,
+ avatar: chat?.avatar,
+ role,
+ members,
+ }),
+ );
+ }
}
}
}, [id, chatList.length]);
@@ -74,6 +115,31 @@ const ChatPage = () => {
}
};
+ const handleChoose = org => {
+ const newSearchParams = new URLSearchParams(searchParams);
+
+ if (org) {
+ newSearchParams.set('org', org.id);
+ navigate('/chat' + `?org=${org.id}`);
+ if (xsMatch) {
+ setChatListIsOpen(true);
+ }
+ } else {
+ newSearchParams.delete('org');
+ }
+
+ if (newSearchParams.get('org') === 'undefined') {
+ newSearchParams.delete('org');
+ setSearchParams(newSearchParams);
+ }
+ };
+
+ useEffect(() => {
+ if (searchParams.get('org')) {
+ dispatch(getChatListByOrg('Organization', searchParams.get('org')));
+ }
+ }, [searchParams.get('org')]);
+
return (
@@ -101,38 +167,97 @@ const ChatPage = () => {
>
-
-
-
- {id ? (
-
- ) : (
-
- setChatListIsOpen(prev => !prev)}
- color="inherit"
- sx={menuButtonSx}
+
+
+ <>
+
-
-
-
-
- Please select a chat to start messaging...
+
+
+
+ >
+ {organizations.map(org => (
+ handleChoose(org)}
+ >
+
+
+
+
+ ))}
+
+
+
+
- )}
+ setChatListIsOpen(false)}
+ />
+ {id ? (
+
+ ) : (
+
+ setChatListIsOpen(prev => !prev)}
+ color="inherit"
+ sx={menuButtonSx}
+ >
+
+
+
+
+ Please select a chat to start messaging...
+
+
+ )}
+
@@ -141,18 +266,109 @@ const ChatPage = () => {
export default ChatPage;
+const orgListItemSx = theme => ({
+ padding: '5px',
+ borderRadius: '8px',
+ cursor: 'pointer',
+ width: '70px',
+ [theme.breakpoints.down('sm')]: {
+ width: '50px',
+ },
+});
+
const layoutSx = theme => ({
paddingY: '10px !important',
- [theme.breakpoints.down('xs')]: {
+ [theme.breakpoints.down('md')]: {
paddingY: '10px !important',
},
});
+const mobileChatListOpenBackground = theme => ({
+ display: 'none',
+ [theme.breakpoints.down('xs')]: {
+ display: 'block',
+ width: '30%',
+ position: 'absolute',
+ top: 0,
+ right: 0,
+ bottom: '-1px',
+ zIndex: 20,
+ background: 'rgba(0, 0, 0, .1)',
+ },
+ [theme.breakpoints.down(500)]: {
+ display: 'none',
+ },
+});
+
+const orgListSx = theme => ({
+ display: 'flex',
+ flexDirection: 'column',
+ minWidth: '72px',
+ border: '2px solid #e5e5e5',
+ borderRight: 'unset',
+ padding: '0 0 5px',
+ overflowY: 'auto',
+ overflowX: 'hidden',
+ height: '100%',
+ '::-webkit-scrollbar': {
+ width: '0px',
+ },
+ [theme.breakpoints.down('sm')]: {
+ minWidth: '52px',
+ },
+});
+
+const leftSideSx = theme => ({
+ width: '30%',
+ display: 'flex',
+ justifyContent: 'end',
+ [theme.breakpoints.down('xs')]: {
+ display: 'none',
+ },
+});
+
+const mobileChatListOpen = theme => ({
+ overflowY: 'auto',
+ '::-webkit-scrollbar': {
+ width: '4px',
+ },
+ [theme.breakpoints.down('xs')]: {
+ background: '#fcfaf6',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ bottom: 0,
+ zIndex: 20,
+ display: 'block',
+ width: '70%',
+ },
+ [theme.breakpoints.down(500)]: {
+ width: '100%',
+ },
+});
+
+const orgAvatarSx = theme => ({
+ width: '60px',
+ height: '60px',
+ [theme.breakpoints.down('sm')]: {
+ width: '40px',
+ height: '40px',
+ },
+ // backgroundColor: '#fff',
+});
+
+const selectedTab = (theme, primary) => ({
+ backgroundColor: primary
+ ? theme.palette.secondary.main
+ : theme.palette.primary.main,
+});
+
const wrapper = theme => ({
minHeight: '300px',
padding: '0px 20px 20px',
position: 'relative',
display: 'flex',
+ maxWidth: 'unset',
flexDirection: 'column',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
@@ -161,11 +377,25 @@ const wrapper = theme => ({
[theme.breakpoints.down(780)]: {
paddingX: '10px',
borderRadius: '0',
+ // [theme.breakpoints.down('xs')]: {
+ // // padding: '20px 40px 50px',
+ // minHeight: '300px',
+ // gap: '8px',
+ // padding: '10px 10px 30px',
+ },
+});
+
+const chatInnerWrapper = theme => ({
+ display: 'flex',
+ flexDirection: 'row',
+ width: '100%',
+ height: 'calc(100vh - 160px)',
+ [theme.breakpoints.down('sm')]: {
+ height: 'calc(100vh - 130px)',
},
});
const chatWrapper = {
- height: 'calc(100vh - 126px)',
width: '100%',
display: 'flex',
border: '2px solid #e5e5e5',
@@ -175,6 +405,9 @@ const chatWrapper = {
const selectLabelWrapper = {
position: 'relative',
width: '70%',
+ // [theme.breakpoints.down(1760)]: {
+ // width: '65%',
+ // },
[theme.breakpoints.down('sm')]: {
width: '100%',
},
diff --git a/src/pages/CreateEditOrganization.jsx b/src/pages/CreateEditOrganization.jsx
new file mode 100644
index 00000000..f31e3dac
--- /dev/null
+++ b/src/pages/CreateEditOrganization.jsx
@@ -0,0 +1,73 @@
+import React, { useEffect, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import Layout from '../styles/Layout.jsx';
+import { CustomCard } from '../components/custom/Card.jsx';
+import Headings from '../router/Headings.jsx';
+import CreateEditOrganizationForm from '../components/forms/create-edit-organization-form/CreateEditOrganizationForm.jsx';
+import { useParams } from 'react-router-dom/dist';
+import {
+ clearOrganization,
+ getOrganizationById,
+} from '../redux/actions/organizationAction.js';
+import ChangeLinkId from '../components/forms/change-link-id/index.jsx';
+import NotFound from './Not-Found.jsx';
+import { Box } from '@mui/material';
+import Loader from '../components/Loader.jsx';
+
+const CreateEditOrganization = () => {
+ const { id } = useParams();
+ const dispatch = useDispatch();
+ const role = useSelector(s => s.user.user.current_role);
+ const organization = useSelector(s => s.organization.organization);
+ const notFound = useSelector(s => s.organization.notFound);
+ const [newLinkId, setNewLinkId] = useState(null);
+ const [showChangeLinkId, setShowChangeLinkId] = useState(false);
+
+ useEffect(() => {
+ if (id) {
+ dispatch(getOrganizationById(id));
+ }
+ return () => {
+ // if (id) {
+ dispatch(clearOrganization());
+ // }
+ };
+ }, [id]);
+
+ if (!notFound) {
+ return (
+
+
+
+
+
+ {organization.id && (
+
+ )}
+
+
+ );
+ } else {
+ return ;
+ }
+};
+
+export default CreateEditOrganization;
+
+const editWrapper = theme => ({
+ padding: '41px 68px 70px',
+ [theme.breakpoints.down('sm')]: {
+ padding: '41px 48px 50px',
+ },
+ [theme.breakpoints.down('xs')]: {
+ padding: '31px 28px 40px',
+ },
+});
diff --git a/src/pages/CustomersPage.jsx b/src/pages/CustomersPage.jsx
new file mode 100644
index 00000000..64503beb
--- /dev/null
+++ b/src/pages/CustomersPage.jsx
@@ -0,0 +1,234 @@
+import React, { useEffect, useState } from 'react';
+import Layout from '../styles/Layout.jsx';
+import { Box, Button, useMediaQuery } from '@mui/material';
+import ArrowBackIcon from '@mui/icons-material/ArrowBack.js';
+import Filter from '../components/forms/filter/index.jsx';
+import { useDispatch, useSelector } from 'react-redux';
+import { useNavigate, useSearchParams } from 'react-router-dom/dist';
+import AuditorListCard from '../components/AuditorListCard.jsx';
+import theme from '../styles/themes.js';
+import CustomPagination from '../components/custom/CustomPagination.jsx';
+import { addTestsLabel } from '../lib/helper.js';
+import Headings from '../router/Headings.jsx';
+import { searchCustomer } from '../redux/actions/customerAction.js';
+import { useLocation } from 'react-router-dom';
+import CustomerListCard from '../components/CustomerListCard.jsx';
+import { getOrganizationById } from '../redux/actions/organizationAction.js';
+import { clearUserMessages } from '../redux/actions/userAction.js';
+import CustomSnackbar from '../components/custom/CustomSnackbar.jsx';
+
+const CustomersPage = () => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const matchSm = useMediaQuery(theme.breakpoints.down('sm'));
+ const matchXs = useMediaQuery(theme.breakpoints.down('xs'));
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [query, setQuery] = useState(undefined);
+ const customers = useSelector(s => s.customer.customers);
+ const totalCustomers = useSelector(s => s.customer.searchTotalCustomers);
+ const [projectIdToInvite, setProjectIdToInvite] = useState(() =>
+ searchParams.get('projectIdToInvite'),
+ );
+ const successMessage = useSelector(s => s.customer.successMessage);
+
+ const location = useLocation();
+ const [currentPage, setCurrentPage] = useState(
+ +searchParams.get('page') || 1,
+ );
+
+ const applyFilter = filter => {
+ setQuery(query => {
+ const { ...data } = query || {};
+ setCurrentPage(1);
+ return {
+ ...data,
+ page: 1,
+ sort: filter.sort || '',
+ sort_by: filter.sort_by || '',
+ search: filter.search || '',
+ tags: filter.tags || [],
+ dateFrom: filter.dateFrom || '',
+ dateTo: filter.dateTo || '',
+ from: filter.price.from || '',
+ to: filter.price.to || '',
+ readyToWait: filter.readyToWait || '',
+ };
+ });
+ dispatch(searchCustomer(filter));
+ };
+
+ const initialFilter = {
+ page: searchParams.get('page') || 1,
+ search: searchParams.get('search') || '',
+ tags: searchParams.getAll('tags') || [],
+ dateFrom: searchParams.get('dateFrom') || new Date(),
+ dateTo: searchParams.get('dateTo') || new Date(),
+ };
+
+ const getNumberOfPages = () => {
+ return Math.ceil(totalCustomers / 10);
+ };
+
+ const handleChangePage = (e, page) => {
+ setCurrentPage(page);
+ setQuery(prev => {
+ const { ...data } = prev || initialFilter;
+ return { ...data, page };
+ });
+ };
+
+ useEffect(() => {
+ if (query) {
+ setSearchParams({ ...query });
+ }
+ }, [query]);
+
+ useEffect(() => {
+ dispatch(searchCustomer(initialFilter));
+ }, [searchParams.toString()]);
+
+ useEffect(() => {
+ setCurrentPage(+searchParams.get('page') || 1);
+ }, [searchParams.toString()]);
+
+ useEffect(() => {
+ dispatch(getOrganizationById(searchParams.get('organization')));
+ }, [searchParams.get('organization')]);
+
+ const previousPath = location.state?.from || '/';
+
+ return (
+
+
+ dispatch(clearUserMessages())}
+ />
+
+
+ navigate(previousPath, { replace: true })}
+ aria-label="Go back"
+ {...addTestsLabel('go-back-button')}
+ >
+
+
+
+
+
+
+ 0}
+ count={getNumberOfPages()}
+ sx={{ mb: '20px' }}
+ page={currentPage}
+ onChange={handleChangePage}
+ showFirstLast={!matchXs}
+ size="small"
+ />
+ {customers?.length > 0 && (
+
+ {customers?.map((customer, idx) => (
+
+
+
+ ))}
+ {!matchSm && customers?.length % 2 === 1 && (
+
+ )}
+
+ )}
+ {customers?.length === 0 && No results}
+ 0}
+ count={getNumberOfPages()}
+ sx={{ display: 'flex', justifyContent: 'flex-end' }}
+ page={currentPage}
+ onChange={handleChangePage}
+ showFirstLast={!matchXs}
+ size="small"
+ />
+
+
+ );
+};
+
+export default CustomersPage;
+
+const wrapper = theme => ({
+ width: '100%',
+ padding: '20px',
+ backgroundColor: '#FCFAF6',
+ border: '1.42857px solid #D9D9D9',
+ boxShadow:
+ '0px 71.4286px 57.1429px rgba(0, 0, 0, 0.07),' +
+ ' 0px 29.8412px 23.8729px rgba(0, 0, 0, 0.0503198), ' +
+ '0px 15.9545px 12.7636px rgba(0, 0, 0, 0.0417275), ' +
+ '0px 8.94397px 7.15517px rgba(0, 0, 0, 0.035), ' +
+ '0px 4.75007px 3.80006px rgba(0, 0, 0, 0.0282725), ' +
+ '0px 1.97661px 1.58129px rgba(0, 0, 0, 0.0196802)',
+ borderRadius: '10.7143px',
+ minHeight: '1000px',
+});
+
+const headWrapper = theme => ({
+ display: 'flex',
+ justifyContent: 'space-between',
+ mb: '20px',
+ [theme.breakpoints.down('sm')]: {
+ mb: '10px',
+ },
+});
+
+const contentWrapper = {
+ display: 'flex',
+ flexWrap: 'wrap',
+ mb: '20px',
+ borderLeft: '1px solid #B2B3B3',
+};
+
+const auditorContainerStyle = idx => ({
+ maxHeight: '200px',
+ minHeight: '150px',
+ borderRight: '1px solid #B2B3B3',
+ borderBottom: '1px solid #B2B3B3',
+ borderTop: idx <= 1 ? '1px solid #B2B3B3' : 'none',
+ width: {
+ zero: '100%',
+ sm: '50%',
+ md: '50%',
+ lg: '50%',
+ },
+ [theme.breakpoints.down('sm')]: {
+ borderTop: idx === 0 ? '1px solid #B2B3B3' : 'none',
+ },
+ [theme.breakpoints.down('xs')]: {
+ height: '130px',
+ },
+});
+
+const fakeContainerStyle = {
+ width: {
+ zero: '100%',
+ sm: '50%',
+ md: '50%',
+ lg: '50%',
+ },
+ border: '0.5px solid #B2B3B3',
+};
+
+const noResults = {
+ paddingTop: '70px',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+};
diff --git a/src/pages/MyOrganizations.jsx b/src/pages/MyOrganizations.jsx
new file mode 100644
index 00000000..3e94def8
--- /dev/null
+++ b/src/pages/MyOrganizations.jsx
@@ -0,0 +1,301 @@
+import React, { useMemo, useEffect } from 'react';
+import {
+ Avatar,
+ Box,
+ Button,
+ Typography,
+ useMediaQuery,
+ Tooltip,
+ Grid,
+ Card,
+ CardMedia,
+ CardContent,
+ CardActions,
+} from '@mui/material';
+import ArrowBackIcon from '@mui/icons-material/ArrowBack.js';
+import theme from '../styles/themes.js';
+import { useNavigate, useParams, Link } from 'react-router-dom/dist';
+import { useDispatch, useSelector } from 'react-redux';
+import { AUDITOR, CUSTOMER } from '../redux/actions/types.js';
+import Layout from '../styles/Layout.jsx';
+import Loader from '../components/Loader.jsx';
+import { CustomCard } from '../components/custom/Card.jsx';
+import OrganizationCard from '../components/OrganizationCard.jsx';
+import Badge from '@mui/material/Badge';
+import { CLEAR_NOT_FOUND_ERROR } from '../redux/actions/types.js';
+
+const MyOrganization = () => {
+ const role = useSelector(s => s.user.user.current_role);
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const matchXs = useMediaQuery(theme.breakpoints.down('xs'));
+ const matchXxs = useMediaQuery(theme.breakpoints.down(590));
+ const own = useSelector(s => s.organization.own);
+ const organizations = useSelector(s => s.organization.includeMe);
+ const invites = useSelector(s => s.organization.invites);
+ const loading = useSelector(s => s.organization.loading);
+ const errorRequest = useSelector(s => s.organization.errorRequest);
+ const {
+ customer,
+ error: customerError,
+ success: customerSuccess,
+ } = useSelector(s => s.customer);
+ const {
+ auditor,
+ error: auditorError,
+ success: auditorSuccess,
+ } = useSelector(s => s.auditor);
+ const { user, error } = useSelector(s => s.user);
+
+ useEffect(() => {
+ return () => {
+ if (errorRequest) {
+ dispatch({type: CLEAR_NOT_FOUND_ERROR});
+ }
+ }
+ }, [errorRequest]);
+
+ return (
+
+
+ navigate(`/${user.current_role[0]}/${user.id}`)}
+ >
+
+
+
+
+ {!organizations.length && !own.length && loading && !errorRequest ? (
+
+
+
+ ) : (
+ <>
+ {!!invites.length && (
+
+ Invites
+
+ {invites?.map(org => {
+ return (
+
+
+
+
+
+ );
+ })}
+
+
+ )}
+ {!!own.length && (
+
+ My organizations
+
+ {own?.map(org => {
+ return (
+
+
+
+ );
+ })}
+
+
+ )}
+ {!!organizations.length && (
+
+ Organizations
+
+ {organizations?.map(org => {
+ return (
+
+
+
+ );
+ })}
+
+
+ )}
+ >
+ )}
+
+ navigate('/create-organization')}
+ variant={'contained'}
+ color={role === CUSTOMER ? 'primary' : 'secondary'}
+ sx={buttonSx}
+ >
+ Create organization
+
+
+
+
+ );
+ // }
+};
+
+export default MyOrganization;
+
+const buttonSx = theme => ({
+ margin: '0 auto',
+ display: 'block',
+ color: theme.palette.background.default,
+ textTransform: 'capitalize',
+ fontWeight: 600,
+ fontSize: '18px',
+ // padding: '9px 50px',
+ width: '234px',
+ borderRadius: '10px',
+ [theme.breakpoints.down('xs')]: {
+ padding: '9px 10px',
+ },
+});
+
+const gridSx = theme => ({
+ mt: '5px!important',
+ marginLeft: '-16px',
+ [theme.breakpoints.down('xs')]: {
+ marginLeft: '-8px',
+ },
+});
+
+const gridItemSx = theme => ({
+ width: '20%',
+ '& .MuiBadge-root': {
+ width: '100%',
+ },
+ '& .org-card': {
+ width: '100%',
+ },
+ [theme.breakpoints.down('md')]: {
+ width: '25%',
+ },
+ [theme.breakpoints.down('sm')]: {
+ width: '33.33%',
+ },
+ [theme.breakpoints.down('xs')]: {
+ width: '50%',
+ paddingTop: '15px',
+ paddingLeft: '15px',
+ },
+});
+
+const wrapper = theme => ({
+ display: 'flex',
+ flexDirection: 'column',
+ width: '100%',
+ position: 'relative',
+ '& ul': {
+ fontSize: '16px',
+ marginBottom: '28px',
+ '& li': {
+ marginLeft: '15px',
+ marginTop: '7px',
+ },
+ },
+ padding: '25px 30px 60px',
+ [theme.breakpoints.down('md')]: {
+ padding: '20px 24px 20px',
+ },
+ [theme.breakpoints.down('sm')]: {
+ gap: '20px',
+ padding: '30px 10px 20px',
+ '& h3': {
+ fontSize: '20px',
+ },
+ },
+ [theme.breakpoints.down(780)]: {
+ borderRadius: '0!important',
+ },
+});
+
+const innerWrapper = theme => ({
+ width: '100%',
+ minHeight: '520px',
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '30px',
+ justifyContent: 'space-between',
+ '& h4': {
+ textAlign: 'center',
+ },
+ [theme.breakpoints.down('sm')]: {},
+ [theme.breakpoints.down('xs')]: {
+ width: '100%',
+ gap: '25px',
+ '& h4': {
+ fontSize: '20px',
+ },
+ '& .mobile-tag-wrapper': {
+ maxWidth: '380px',
+ },
+ },
+});
+
+const avatarSx = theme => ({
+ height: '100px',
+ width: '100px',
+ padding: '5px',
+ border: `1px solid ${theme.palette.primary.main}`,
+ [theme.breakpoints.down('sm')]: {
+ width: '80px',
+ height: '80px',
+ },
+ [theme.breakpoints.down('xs')]: {
+ width: '50px',
+ height: '50px',
+ },
+});
+
+const organizationSx = theme => ({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ [theme.breakpoints.down('sm')]: {
+ '& p': {
+ fontSize: '14px',
+ },
+ },
+});
+
+const contentWrapper = theme => ({
+ display: 'flex',
+ gap: '30px',
+ flexDirection: 'column',
+ alignItems: 'center',
+ '& a': {
+ textDecoration: 'none',
+ color: 'black',
+ },
+});
diff --git a/src/pages/Public-profile.jsx b/src/pages/Public-profile.jsx
index c72a23be..8b9fd10f 100644
--- a/src/pages/Public-profile.jsx
+++ b/src/pages/Public-profile.jsx
@@ -48,6 +48,7 @@ import UserFeedbacks from '../components/UserFeedbacks.jsx';
import WalletConnectIcon from '../components/icons/WalletConnectIcon.jsx';
import { getPublicAuditsAuditor } from '../redux/actions/auditAction.js';
import ProjectCardList from '../components/Project-card-list.jsx';
+import TypeChat from '../components/Chat/TypeChat.jsx';
const PublicProfile = ({ notFoundRedirect = true }) => {
const navigate = useNavigate();
@@ -552,16 +553,17 @@ const PublicProfile = ({ notFoundRedirect = true }) => {
{data.kind !== 'badge' &&
isAuth() &&
data?.user_id !== user?.id && (
- handleSendMessage(data)}
- {...addTestsLabel('message-button')}
- >
-
-
+ // handleSendMessage(data)}
+ // {...addTestsLabel('message-button')}
+ // >
+ //
+ //
+
)}
{role === AUDITOR && (
diff --git a/src/pages/audit-info.jsx b/src/pages/audit-info.jsx
index a3aa960a..b2aca5cf 100644
--- a/src/pages/audit-info.jsx
+++ b/src/pages/audit-info.jsx
@@ -64,6 +64,7 @@ const AuditInfo = ({
const navigate = useNavigate();
const dispatch = useDispatch();
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false);
+ const [isModalOpen, setIsModalOpen] = useState(false);
const { successMessage, error } = useSelector(s => s.audits);
const { user } = useSelector(s => s.user);
const { chatList } = useSelector(s => s.chat);
@@ -73,7 +74,11 @@ const AuditInfo = ({
const [isFeedbackModalOpen, setIsFeedbackModalOpen] = useState(false);
const handleConfirm = () => {
- dispatch(confirmAudit(audit, true));
+ if (audit?.auditor_organization) {
+ dispatch(confirmAudit({ ...audit, auditor_organization: audit?.auditor_organization.id }, true));
+ } else {
+ dispatch(confirmAudit(audit, true));
+ }
};
const handleDecline = () => {
@@ -262,6 +267,22 @@ const AuditInfo = ({
isPublic ? { alignItems: 'flex-start' } : {},
]}
>
+ {
+ audit?.auditor_organization ?
+
+ :
+ }
{!!audit?.time?.from && !isPublic && (
Time for project:
diff --git a/src/pages/profile-page.jsx b/src/pages/profile-page.jsx
index 8b40d764..eff0fcb9 100644
--- a/src/pages/profile-page.jsx
+++ b/src/pages/profile-page.jsx
@@ -15,6 +15,7 @@ import CustomSnackbar from '../components/custom/CustomSnackbar.jsx';
import { isAuth } from '../lib/helper.js';
import PublicProfile from './Public-profile.jsx';
import NotFound from './Not-Found.jsx';
+import Organization from '../components/Organization.jsx';
const ProfilePage = () => {
const dispatch = useDispatch();
@@ -31,21 +32,25 @@ const ProfilePage = () => {
}, [tab]);
if (linkId && role) {
- if (/^c|a$/i.test(role)) {
- const userLinkId =
- user?.current_role === AUDITOR ? auditor?.link_id : customer?.link_id;
- if (
- !isAuth() ||
- (user?.id &&
- linkId.toLowerCase() !== userLinkId?.toLowerCase() &&
- linkId.toLowerCase() !== user?.id)
- ) {
- return ;
- } else if (
- (user?.current_role === AUDITOR && !/^a$/i.test(role)) ||
- (user?.current_role === CUSTOMER && !/^c$/i.test(role))
- ) {
- return ;
+ if (/^c|a|o$/i.test(role)) {
+ if (role !== 'o') {
+ const userLinkId =
+ user?.current_role === AUDITOR ? auditor?.link_id : customer?.link_id;
+ if (
+ !isAuth() ||
+ (user?.id &&
+ linkId.toLowerCase() !== userLinkId?.toLowerCase() &&
+ linkId.toLowerCase() !== user?.id)
+ ) {
+ return ;
+ } else if (
+ (user?.current_role === AUDITOR && !/^a$/i.test(role)) ||
+ (user?.current_role === CUSTOMER && !/^c$/i.test(role))
+ ) {
+ return ;
+ }
+ } else {
+ return ;
}
} else {
return ;
diff --git a/src/redux/actions/auditAction.js b/src/redux/actions/auditAction.js
index 6e9892ee..8021c83e 100644
--- a/src/redux/actions/auditAction.js
+++ b/src/redux/actions/auditAction.js
@@ -21,6 +21,7 @@ import {
GET_AUDITS_OF_AUDITOR,
GET_PUBLIC_AUDIT,
GET_PUBLIC_REPORT,
+ GET_ORGANIZATION_AUDITS,
GET_REQUEST,
IN_PROGRESS,
NOT_FOUND,
@@ -33,6 +34,7 @@ import {
SET_CURRENT_AUDIT_PARTNER,
UPDATE_AUDIT,
VERIFY_AUDIT_REPORT,
+ GET_ORG_AUDIT_REQUEST,
} from './types.js';
import { history } from '../../services/history.js';
import { ASSET_URL } from '../../services/urls.js';
@@ -70,6 +72,23 @@ export const createRequest = (values, redirect, navigateTo, stay) => {
};
};
+export const getOrganizationAuditRequests = (org_id) => {
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios.get(`${API_URL}/audit_request/organization/id/${org_id}`, {
+ headers: { Authorization: `Bearer ${token}` },
+ })
+ .then(({ data }) => {
+ dispatch({ type: GET_ORG_AUDIT_REQUEST, payload: data });
+ })
+ .catch(({ response }) => {
+ console.log(response, 'res');
+ dispatch({ type: REQUEST_ERROR });
+ });
+ };
+};
+
+
export const createRequestModal = values => {
return dispatch => {
const token = Cookies.get('token');
@@ -147,6 +166,17 @@ export const getAudits = role => {
};
};
+export const getOrganizationAudits = () => {
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios.get(`${API_URL}/audit/organization/all`, {
+ headers: { Authorization: `Bearer ${token}` },
+ })
+ .then(({ data }) => {
+ dispatch({ type: GET_ORGANIZATION_AUDITS, payload: data });
+ });
+ };
+};
export const getPublicAudit = (id, code) => {
return dispatch => {
const token = Cookies.get('token');
diff --git a/src/redux/actions/auditorAction.js b/src/redux/actions/auditorAction.js
index 755386b6..b65dcbb6 100644
--- a/src/redux/actions/auditorAction.js
+++ b/src/redux/actions/auditorAction.js
@@ -177,9 +177,9 @@ export const getAuditorRating = (id, getDetails = false) => {
axios.get(url).then(({ data }) => {
dispatch({ type: GET_AUDITOR_RATING_DETAILS, payload: data });
- });
- };
-};
+ })
+ }
+}
export const searchAuditor = (values, badges = true) => {
const kind = badges ? 'auditor badge' : 'auditor';
@@ -196,10 +196,10 @@ export const searchAuditor = (values, badges = true) => {
dispatch({ type: GET_AUDITORS, payload: data });
})
.catch(({ response }) => {
- console.error(response, 'res');
- });
- };
-};
+ console.error(response, 'res')
+ })
+ }
+}
export const deleteBadgeProfile = id => {
return dispatch => {
diff --git a/src/redux/actions/chatActions.js b/src/redux/actions/chatActions.js
index d35baacd..8898ff75 100644
--- a/src/redux/actions/chatActions.js
+++ b/src/redux/actions/chatActions.js
@@ -16,6 +16,7 @@ import {
CHAT_SET_ERROR,
CHAT_DELETE_MESSAGE,
RECEIVE_NEW_CHAT,
+ CHAT_GET_LIST_ORG,
} from './types.js';
export const getChatList = role => {
@@ -29,6 +30,19 @@ export const getChatList = role => {
};
};
+export const getChatListByOrg = (role, id) => {
+ const token = Cookies.get('token');
+ return dispatch => {
+ axios
+ .get(`${API_URL}/chat/preview/${role}?org_id=${id}`, {
+ headers: { Authorization: `Bearer ${token}` },
+ })
+ .then(({ data }) => {
+ dispatch({ type: CHAT_GET_LIST_ORG, payload: data });
+ });
+ };
+};
+
export const getChatMessages = (chatId, userId) => {
const token = Cookies.get('token');
return dispatch => {
@@ -69,7 +83,6 @@ export const setCurrentChat = (
const previousChatId = chat?.currentChat?.chatId;
if (previousChatId === chatId) return;
-
if (previousChatId) {
const token = Cookies.get('token');
axios.patch(
@@ -104,7 +117,14 @@ export const setCurrentChat = (
};
};
-export const chatSendMessage = (text, to, fromRole, isFirst, kind = 'Text') => {
+export const chatSendMessage = (
+ text,
+ to,
+ fromRole,
+ isFirst,
+ kind = 'Text',
+ from_org_id,
+) => {
const token = Cookies.get('token');
const user = JSON.parse(localStorage.getItem('user'));
@@ -123,6 +143,7 @@ export const chatSendMessage = (text, to, fromRole, isFirst, kind = 'Text') => {
role: fromRole,
text,
kind,
+ from_org_id: from_org_id,
};
} else {
values = {
@@ -130,6 +151,7 @@ export const chatSendMessage = (text, to, fromRole, isFirst, kind = 'Text') => {
role: fromRole,
text,
kind,
+ from_org_id: from_org_id,
};
}
diff --git a/src/redux/actions/customerAction.js b/src/redux/actions/customerAction.js
index 1c1fc8d5..0f5ac3bc 100644
--- a/src/redux/actions/customerAction.js
+++ b/src/redux/actions/customerAction.js
@@ -104,6 +104,26 @@ export const createCustomer = values => {
};
};
+export const searchCustomer = (values, badges = true) => {
+ const kind = 'customer';
+ const queryString = createSearchValues(values, kind);
+
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios
+ .get(
+ `${API_URL}/search?${queryString}`,
+ isAuth() ? { headers: { Authorization: `Bearer ${token}` } } : {},
+ )
+ .then(({ data }) => {
+ dispatch({ type: GET_CUSTOMERS, payload: data });
+ })
+ .catch(({ response }) => {
+ console.error(response, 'res');
+ });
+ };
+};
+
export const updateCustomer = (values, redirect = true) => {
const token = Cookies.get('token');
return dispatch => {
diff --git a/src/redux/actions/organizationAction.js b/src/redux/actions/organizationAction.js
new file mode 100644
index 00000000..136f14d5
--- /dev/null
+++ b/src/redux/actions/organizationAction.js
@@ -0,0 +1,220 @@
+import Cookies from 'js-cookie';
+import axios from 'axios';
+import {
+ ACCEPT_INVITE,
+ ADD_MEMBER_IN_ORGANIZATION,
+ CLEAR_ORGANIZATION,
+ CREATE_ORGANIZATION,
+ DELETE_INVITES,
+ GET_AUDITORS,
+ GET_MY_ORGANIZATION,
+ GET_ORGANIZATION_AUDIT_REQUESTS,
+ GET_ORGANIZATION_BY_ID,
+ GET_ORGANIZATIONS,
+ NOT_FOUND_ORGANIZATION,
+ UPDATE_ORGANIZATION,
+ ERROR_GET_MY_ORGANIZATIONS,
+} from './types.js';
+import { history } from '../../services/history.js';
+import createSearchValues from '../../lib/createSearchValues.js';
+import { isAuth } from '../../lib/helper.js';
+
+const API_URL = import.meta.env.VITE_API_BASE_URL;
+
+export const createOrganization = (value, navigateTo) => {
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios
+ .post(`${API_URL}/organization`, value, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ .then(({ data }) => {
+ dispatch({ type: CREATE_ORGANIZATION, payload: data });
+ if (navigateTo) {
+ history.push({ pathname: navigateTo }, { some: true });
+ }
+ })
+ .catch(({ response }) => {
+ console.log(response, 'res');
+ });
+ };
+};
+
+export const updateOrganization = (value, navigateTo) => {
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios
+ .patch(`${API_URL}/organization/${value.id}`, value, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ .then(({ data }) => {
+ dispatch({ type: UPDATE_ORGANIZATION, payload: data });
+ if (navigateTo) {
+ history.push({ pathname: `/o/${data.link_id}` }, { some: true });
+ }
+ })
+ .catch(({ response }) => {
+ console.log(response, 'res');
+ });
+ };
+};
+
+export const clearOrganization = () => {
+ return dispatch => {
+ dispatch({ type: CLEAR_ORGANIZATION });
+ };
+};
+
+export const getMyOrganizations = () => {
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios
+ .get(`${API_URL}/my_organizations`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ .then(({ data }) => {
+ dispatch({ type: GET_MY_ORGANIZATION, payload: data });
+ })
+ .catch(({ response }) => {
+ dispatch({ type: ERROR_GET_MY_ORGANIZATIONS });
+ console.log(response, 'res');
+ });
+ };
+};
+
+export const searchOrganization = (values, badges = true) => {
+ const queryString = createSearchValues(values, 'Organization');
+
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios
+ .get(
+ `${API_URL}/search?${queryString}`,
+ isAuth() ? { headers: { Authorization: `Bearer ${token}` } } : {},
+ )
+ .then(({ data }) => {
+ dispatch({ type: GET_ORGANIZATIONS, payload: data });
+ })
+ .catch(({ response }) => {
+ console.error(response, 'res');
+ });
+ };
+};
+
+export const getOrganizationById = id => {
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios
+ .get(`${API_URL}/organization/link_id/${id}`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ .then(({ data }) => {
+ dispatch({ type: GET_ORGANIZATION_BY_ID, payload: data });
+ })
+ .catch(e => dispatch({ type: NOT_FOUND_ORGANIZATION }));
+ };
+};
+
+export const addUserInOrganization = (orgLinkId, data, id) => {
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios
+ .post(`${API_URL}/organization/${id}/members`, data, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ .then(({ data }) => {
+ dispatch({ type: ADD_MEMBER_IN_ORGANIZATION });
+ dispatch(getOrganizationById(orgLinkId));
+ });
+ };
+};
+
+export const changeAccessLevel = (org_id, user_id, data, orgLinkId) => {
+ const token = Cookies.get('token');
+ return dispatch => {
+ axios
+ .patch(`${API_URL}/organization/${org_id}/members/${user_id}`, data, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ .then(({ data }) => {
+ dispatch(getOrganizationById(orgLinkId));
+ });
+ };
+};
+
+export const deleteUserFromOrganization = (orgId, userId, linkId) => {
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios
+ .delete(`${API_URL}/organization/${orgId}/members/${userId}`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ .then(({ data }) => {
+ dispatch(getOrganizationById(linkId));
+ });
+ };
+};
+
+export const acceptInvites = org_id => {
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios
+ .post(
+ `${API_URL}/organization/${org_id}/invites/confirm`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ },
+ )
+ .then(({ data }) => {
+ dispatch({ type: ACCEPT_INVITE, payload: data });
+ });
+ };
+};
+
+export const getAuditRequests = org_id => {
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios
+ .get(`${API_URL}/audit_request/organization/all`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ .then(({ data }) => {
+ dispatch({ type: GET_ORGANIZATION_AUDIT_REQUESTS, payload: data });
+ });
+ };
+};
+
+export const deleteInvites = (org_id, user_id) => {
+ return dispatch => {
+ const token = Cookies.get('token');
+ axios
+ .delete(`${API_URL}/organization/${org_id}/invites/${user_id}`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ .then(({ data }) => {
+ dispatch({ type: DELETE_INVITES, payload: { ...data, id: org_id } });
+ });
+ // dispatch({ type: 'CHECK_INVITES', payload: data });
+ };
+};
diff --git a/src/redux/actions/types.js b/src/redux/actions/types.js
index d521e7f2..f44e6206 100644
--- a/src/redux/actions/types.js
+++ b/src/redux/actions/types.js
@@ -15,6 +15,7 @@ export const AUDITOR_SET_ERROR = 'AUDITOR_SET_ERROR';
export const CUSTOMER_SET_ERROR = 'CUSTOMER_SET_ERROR';
export const GET_CUSTOMER = 'GET_CUSTOMER';
export const GET_CUSTOMERS = 'GET_CUSTOMERS';
+export const GET_ORGANIZATIONS = 'GET_ORGANIZATIONS';
export const GET_AUDITOR = 'GET_AUDITOR';
export const GET_AUDITORS = 'GET_AUDITORS';
export const AUDITOR = 'auditor';
@@ -70,8 +71,11 @@ export const VERIFY_AUDIT_REPORT = 'VERIFY_AUDIT_REPORT';
export const RESTORE_PASSWORD = 'RESTORE_PASSWORD';
export const SEND_EMAIL = 'SEND_EMAIL';
export const RECEIVE_NEW_CHAT = 'RECEIVE_NEW_CHAT';
+export const ORGANIZATION_INVITE = 'ORGANIZATION_INVITE';
export const WEBSOCKET_CONNECT = 'WEBSOCKET_CONNECT';
export const WEBSOCKET_DISCONNECT = 'WEBSOCKET_DISCONNECT';
+export const CLEAR_SEARCH = 'CLEAR_SEARCH';
+export const WEBSOCKET_SEND = 'WEBSOCKET_SEND';
export const CLEAR_PROJECT = 'CLEAR_PROJECT';
export const WEBSOCKET_CONNECTED = 'WEBSOCKET_CONNECTED';
export const RECEIVE_AUDITOR_MESSAGE = 'RECEIVE_AUDITOR_MESSAGE';
@@ -106,6 +110,7 @@ export const NEED_UPDATE = 'NEED_UPDATE';
export const MERGE_ACCOUNT = 'MERGE_ACCOUNT';
export const DELETE_BADGE = 'DELETE_BADGE';
export const CHAT_GET_LIST = 'CHAT_GET_LIST';
+export const CHAT_GET_LIST_ORG = 'CHAT_GET_LIST_ORG';
export const CHAT_GET_MESSAGES = 'CHAT_GET_MESSAGES';
export const CHAT_NEW_MESSAGE = 'CHAT_NEW_MESSAGE';
export const CHAT_DELETE_MESSAGE = 'CHAT_DELETE_MESSAGE';
@@ -154,11 +159,25 @@ export const EDIT_AUDIT_REQUEST_CUSTOMER = 'EDIT_AUDIT_REQUEST_CUSTOMER';
export const NEED_TO_AUTH_GITHUB = 'NEED_TO_AUTH_GITHUB';
export const SWITCH_REPO = 'SWITCH_REPO';
export const NOT_FOUND_REPOS = 'NOT_FOUND_REPOS';
+export const CREATE_ORGANIZATION = 'CREATE_ORGANIZATION';
+export const UPDATE_ORGANIZATION = 'UPDATE_ORGANIZATION';
+export const CLEAR_ORGANIZATION = 'CLEAR_ORGANIZATION';
+export const GET_ORGANIZATION_BY_ID = 'GET_ORGANIZATION_BY_ID';
+export const GET_MY_ORGANIZATION = 'GET_MY_ORGANIZATION';
+export const ERROR_GET_MY_ORGANIZATIONS = 'ERROR_GET_MY_ORGANIZATIONS';
export const CLEAR_NOT_FOUND_ERROR = 'CLEAR_NOT_FOUND_ERROR';
export const GET_AUDIT_HISTORY = 'GET_AUDIT_HISTORY';
+export const GET_ORG_AUDIT_REQUEST = 'GET_ORG_AUDIT_REQUEST';
export const READ_AUDIT_HISTORY = 'READ_AUDIT_HISTORY';
export const READ_AUDIT_REQUEST_HISTORY = 'READ_AUDIT_REQUEST_HISTORY';
+export const ADD_MEMBER_IN_ORGANIZATION = 'ADD_MEMBER_IN_ORGANIZATION';
export const EDIT_AUDIT_CUSTOMER = 'EDIT_AUDIT_CUSTOMER';
+export const GET_ORGANIZATION_AUDITS = 'GET_ORGANIZATION_AUDITS';
+export const DELETE_INVITES = 'DELETE_INVITES';
+export const ACCEPT_INVITE = 'ACCEPT_INVITE';
+export const NOT_FOUND_ORGANIZATION = 'NOT_FOUND_ORGANIZATION';
+export const GET_ORGANIZATION_AUDIT_REQUESTS =
+ 'GET_ORGANIZATION_AUDIT_REQUESTS';
export const GET_AUDIT_REQUEST_HISTORY = 'GET_AUDIT_REQUEST_HISTORY';
export const GET_AUDITS_OF_AUDITOR = 'GET_AUDITS_OF_AUDITOR';
export const GET_PUBLIC_AUDIT = 'GET_PUBLIC_AUDIT';
diff --git a/src/redux/actions/userAction.js b/src/redux/actions/userAction.js
index 9d45b2e5..444a0bcc 100644
--- a/src/redux/actions/userAction.js
+++ b/src/redux/actions/userAction.js
@@ -31,6 +31,8 @@ import {
CLEAR_MESSAGES,
AUDITOR,
CUSTOMER,
+ GET_AUDITS,
+ CREATE_ORGANIZATION,
} from './types.js';
import { savePublicReport } from './auditAction.js';
@@ -568,6 +570,49 @@ export const changeRolePublicAuditorNoRedirect = (role, id) => {
};
};
+export const changeRoleCreateOrganization = (role, id, value, navigateTo) => {
+ return dispatch => {
+ axios
+ .patch(
+ `${API_URL}/user/${id}`,
+ { current_role: role },
+ {
+ headers: {
+ Authorization: 'Bearer ' + Cookies.get('token'),
+ 'Content-Type': 'application/json',
+ },
+ },
+ )
+ .then(({ data }) => {
+ dispatch({ type: SELECT_ROLE, payload: data });
+ localStorage.setItem('user', JSON.stringify(data));
+ const token = Cookies.get('token');
+ if (data?.name) {
+ axios
+ .post(`${API_URL}/organization`, value, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ .then(({ data: orgData }) => {
+ dispatch({ type: CREATE_ORGANIZATION, payload: orgData });
+ if (navigateTo) {
+ history.push(
+ { pathname: `${data.current_role[0]}/${data.id}` },
+ { some: true },
+ );
+ }
+ })
+ .catch(({ response }) => {
+ console.log(response, 'res');
+ });
+ } else {
+ history.push({ pathname: '/edit-profile' }, { some: true });
+ }
+ });
+ };
+};
+
export const changeRolePublicCustomerNoRedirect = (role, id) => {
return dispatch => {
axios
diff --git a/src/redux/middleware/websocketMiddleware.js b/src/redux/middleware/websocketMiddleware.js
index ded24ceb..b3727f08 100644
--- a/src/redux/middleware/websocketMiddleware.js
+++ b/src/redux/middleware/websocketMiddleware.js
@@ -7,6 +7,7 @@ import {
GET_NEW_REQUEST,
IN_PROGRESS,
NEED_UPDATE,
+ ORGANIZATION_INVITE,
REQUEST_DECLINE,
UPDATE_AUDIT_ISSUE_WS,
WEBSOCKET_CONNECT,
@@ -92,7 +93,7 @@ const websocketMiddleware = () => {
} else if (message.kind.toLowerCase() === 'chatmessage') {
const sameRole =
store.getState().user.user.current_role.toLowerCase() ===
- message.user_role.toLowerCase();
+ message?.user_role?.toLowerCase();
store.dispatch(
receiveNewChatMessage(message.payload.ChatMessage, sameRole),
);
@@ -136,6 +137,11 @@ const websocketMiddleware = () => {
payload: { issue: payload.issue, auditId: payload.audit },
});
}
+ } else if (message.kind.toLowerCase() === 'organizationinvite') {
+ store.dispatch({
+ type: ORGANIZATION_INVITE,
+ payload: message.payload.OrganizationInvite,
+ });
}
};
diff --git a/src/redux/reducers/auditReducer.js b/src/redux/reducers/auditReducer.js
index 061e6d29..36218c0f 100644
--- a/src/redux/reducers/auditReducer.js
+++ b/src/redux/reducers/auditReducer.js
@@ -36,6 +36,9 @@ import {
VERIFY_AUDIT_REPORT,
UPDATE_AUDIT,
CREATE_AUDIT_ISSUE,
+ GET_ORGANIZATION_AUDIT_REQUESTS,
+ GET_ORG_AUDIT_REQUEST,
+ GET_ORGANIZATION_AUDITS
} from '../actions/types.js';
const initialState = {
@@ -52,6 +55,8 @@ const initialState = {
approvedHistory: null,
unreadHistory: null,
auditRequestHistory: [],
+ organizationAuditRequests: [],
+ organizationAudits: [],
verifyAudit: null,
};
@@ -64,6 +69,16 @@ export const auditReducer = (state = initialState, action) => {
successMessage: 'Audit request created successfully',
auditRequest: action.payload,
};
+ case GET_ORG_AUDIT_REQUEST:
+ return {
+ ...state,
+ organizationAuditRequests: [...state.organizationAuditRequests, ...action.payload],
+ };
+ case GET_ORGANIZATION_AUDITS:
+ return {
+ ...state,
+ organizationAudits: [...state.organizationAudits, ...action.payload],
+ };
case GET_AUDIT_REQUEST:
return { ...state, auditRequests: action.payload };
case DELETE_REQUEST:
@@ -96,6 +111,14 @@ export const auditReducer = (state = initialState, action) => {
request => request.id !== action.payload.id,
),
};
+ case GET_ORGANIZATION_AUDIT_REQUESTS:
+ return {
+ ...state,
+ organizationAuditRequests: [
+ ...state.organizationAuditRequests,
+ ...action.payload,
+ ],
+ };
case EDIT_AUDIT:
return {
...state,
diff --git a/src/redux/reducers/auditorReducer.js b/src/redux/reducers/auditorReducer.js
index 68de033b..76474e92 100644
--- a/src/redux/reducers/auditorReducer.js
+++ b/src/redux/reducers/auditorReducer.js
@@ -12,6 +12,7 @@ import {
GET_AUDITOR_RATING_DETAILS,
CLEAR_CURRENT_AUDITOR_CUSTOMER,
CLEAR_SEARCHED_AUDITOR,
+ CLEAR_SEARCH,
} from '../actions/types.js';
const initialState = {
@@ -59,6 +60,12 @@ export const auditorReducer = (state = initialState, action) => {
...state,
currentAuditor: action.payload,
};
+ case CLEAR_SEARCH:
+ return {
+ ...state,
+ searchAuditors: null,
+ searchTotalAuditors: 0,
+ };
case SELECT_ROLE:
return { ...state, currentAuditor: null };
case LOG_OUT:
diff --git a/src/redux/reducers/chatReducer.js b/src/redux/reducers/chatReducer.js
index cfcdb0a4..b517cc9d 100644
--- a/src/redux/reducers/chatReducer.js
+++ b/src/redux/reducers/chatReducer.js
@@ -3,6 +3,7 @@ import {
CHAT_CLOSE_CURRENT_CHAT,
CHAT_DELETE_MESSAGE,
CHAT_GET_LIST,
+ CHAT_GET_LIST_ORG,
CHAT_GET_MESSAGES,
CHAT_NEW_MESSAGE,
CHAT_SEND_FIRST_MESSAGE,
@@ -18,6 +19,7 @@ import {
const initialState = {
chatList: [],
chatMessages: [],
+ orgChatList: [],
currentChat: null,
unreadMessages: 0,
differentRoleUnreadMessages: 0,
@@ -40,6 +42,12 @@ export const chatReducer = (state = initialState, action) => {
),
};
}
+ case CHAT_GET_LIST_ORG: {
+ return {
+ ...state,
+ orgChatList: action.payload,
+ };
+ }
case RECEIVE_NEW_CHAT:
return {
...state,
diff --git a/src/redux/reducers/customerReducer.js b/src/redux/reducers/customerReducer.js
index 6446a2b8..f4c15e36 100644
--- a/src/redux/reducers/customerReducer.js
+++ b/src/redux/reducers/customerReducer.js
@@ -1,6 +1,7 @@
import {
CLEAR_CURRENT_AUDITOR_CUSTOMER,
CLEAR_MESSAGES,
+ CLEAR_SEARCH,
CUSTOMER_SET_ERROR,
GET_CURRENT_CUSTOMER,
GET_CUSTOMER,
@@ -49,6 +50,12 @@ export const customerReducer = (state = initialState, action) => {
...state,
error: action.payload,
};
+ case CLEAR_SEARCH:
+ return {
+ ...state,
+ searchCustomers: null,
+ searchTotalCustomers: 0,
+ };
case CLEAR_MESSAGES:
return {
...state,
diff --git a/src/redux/reducers/organizationReducer.js b/src/redux/reducers/organizationReducer.js
new file mode 100644
index 00000000..32439141
--- /dev/null
+++ b/src/redux/reducers/organizationReducer.js
@@ -0,0 +1,127 @@
+import {
+ CREATE_ORGANIZATION,
+ GET_MY_ORGANIZATION,
+ GET_ORGANIZATION_BY_ID,
+ UPDATE_ORGANIZATION,
+ CLEAR_ORGANIZATION,
+ DELETE_INVITES,
+ ACCEPT_INVITE,
+ NOT_FOUND_ORGANIZATION,
+ ADD_MEMBER_IN_ORGANIZATION,
+ CLEAR_MESSAGES,
+ GET_ORGANIZATIONS,
+ ORGANIZATION_INVITE,
+ CLEAR_SEARCH,
+ ERROR_GET_MY_ORGANIZATIONS,
+ CLEAR_NOT_FOUND_ERROR
+} from '../actions/types.js';
+
+const initialState = {
+ organizations: [],
+ own: [],
+ searchOrganizations: [],
+ includeMe: [],
+ organization: {},
+ invites: [],
+ notFound: false,
+ successMessage: '',
+ loading: true,
+ errorRequest: false,
+};
+
+export const organizationReducer = (state = initialState, action) => {
+ switch (action.type) {
+ case GET_MY_ORGANIZATION:
+ return {
+ ...state,
+ organizations: [...action.payload.owner, ...action.payload.member],
+ own: action.payload.owner,
+ includeMe: action.payload.member,
+ invites: action.payload.invites,
+ loading: false,
+ };
+ case ERROR_GET_MY_ORGANIZATIONS:
+ return {
+ ...state,
+ errorRequest: true,
+ };
+ case CLEAR_NOT_FOUND_ERROR:
+ return {
+ ...state,
+ errorRequest: false,
+ };
+ case ADD_MEMBER_IN_ORGANIZATION:
+ return {
+ ...state,
+ successMessage: 'User successfully invited',
+ };
+ case GET_ORGANIZATIONS: {
+ return {
+ ...state,
+ searchOrganizations: action.payload.result,
+ };
+ }
+ case CLEAR_MESSAGES:
+ return {
+ ...state,
+ successMessage: '',
+ };
+ case CREATE_ORGANIZATION:
+ return {
+ ...state,
+ organization: action.payload,
+ organizations: [...state.organizations, action.payload],
+ own: [...state.own, action.payload],
+ };
+ case NOT_FOUND_ORGANIZATION:
+ return {
+ ...state,
+ notFound: true,
+ };
+ case UPDATE_ORGANIZATION:
+ return {
+ ...state,
+ organization: action.payload,
+ own: state.own.map(el =>
+ el.id === action.payload.id ? action.payload : el,
+ ),
+ organizations: state.organizations.map(el =>
+ el.id === action.payload.id ? action.payload : el,
+ ),
+ };
+ case GET_ORGANIZATION_BY_ID:
+ return {
+ ...state,
+ organization: action.payload,
+ };
+ case ORGANIZATION_INVITE:
+ return {
+ ...state,
+ invites: [...state.invites, action.payload],
+ };
+ case DELETE_INVITES:
+ return {
+ ...state,
+ invites: state.invites.filter(el => el.id !== action.payload.id),
+ };
+ case CLEAR_SEARCH:
+ return {
+ ...state,
+ searchOrganizations: [],
+ };
+ case ACCEPT_INVITE:
+ return {
+ ...state,
+ invites: state.invites.filter(el => el.id !== action.payload.id),
+ organizations: [...state.organizations, action.payload],
+ organization: action.payload,
+ };
+ case CLEAR_ORGANIZATION:
+ return {
+ ...state,
+ organization: {},
+ };
+ default:
+ return state;
+ }
+};
diff --git a/src/redux/store.js b/src/redux/store.js
index 2e09ea08..9d74c127 100644
--- a/src/redux/store.js
+++ b/src/redux/store.js
@@ -14,6 +14,7 @@ import { notFoundReducer } from './reducers/notFoundReducer.js';
import { chatReducer } from './reducers/chatReducer.js';
import { githubReducer } from './reducers/githubReducer.js';
import { filterConfig } from './reducers/configReducer.js';
+import { organizationReducer } from './reducers/organizationReducer.js';
export const store = createStore(
combineReducers({
@@ -29,6 +30,7 @@ export const store = createStore(
chat: chatReducer,
github: githubReducer,
filter: filterConfig,
+ organization: organizationReducer,
}),
composeWithDevTools(applyMiddleware(thunk, websocketMiddleware())),
);
diff --git a/src/routes/AppRoutes.jsx b/src/routes/AppRoutes.jsx
index 4db3a8cb..d328a43b 100644
--- a/src/routes/AppRoutes.jsx
+++ b/src/routes/AppRoutes.jsx
@@ -14,7 +14,7 @@ import { getCustomer } from '../redux/actions/customerAction.js';
import ProjectPage from '../pages/Project-page.jsx';
import AuditRequestPage from '../pages/Audit-Request-Page.jsx';
import { getProjects } from '../redux/actions/projectAction.js';
-import { getAudits, getAuditsRequest } from '../redux/actions/auditAction.js';
+import { getAudits, getAuditsRequest, getOrganizationAudits } from '../redux/actions/auditAction.js';
import EditProject from '../pages/EditProject.jsx';
import ForCustomer from '../pages/For-customer.jsx';
import ForAuditor from '../pages/For-auditor.jsx';
@@ -53,6 +53,14 @@ import UserProjects from '../pages/UserProjects.jsx';
import PriceCalculationPage from '../pages/PriceCalculationPage.jsx';
import { refreshToken } from '../redux/actions/userAction.js';
import Audit from '../pages/Audit.jsx';
+import Organization from '../components/Organization.jsx';
+import CreateEditOrganization from '../pages/CreateEditOrganization.jsx';
+import {
+ getAuditRequests,
+ getMyOrganizations,
+} from '../redux/actions/organizationAction.js';
+import MyOrganization from '../pages/MyOrganizations.jsx';
+import CustomersPage from '../pages/CustomersPage.jsx';
const AppRoutes = () => {
const currentRole = useSelector(s => s.user.user.current_role);
@@ -61,6 +69,7 @@ const AppRoutes = () => {
const dispatch = useDispatch();
const { reconnect, connected, needUpdate } = useSelector(s => s.websocket);
const [isOpen, setIsOpen] = React.useState(false);
+ const organizations = useSelector(s => s.organization.organizations);
useEffect(() => {
const refreshInterval = setInterval(() => {
@@ -74,12 +83,25 @@ const AppRoutes = () => {
return () => clearInterval(refreshInterval);
}, [isAuth()]);
+ useEffect(() => {
+ if (isAuth()) {
+ dispatch(getMyOrganizations());
+ }
+ }, [isAuth(), currentRole]);
+
+ useEffect(() => {
+ if (organizations?.length) {
+ dispatch(getAuditRequests());
+ }
+ }, [organizations]);
+
useEffect(() => {
if (isAuth()) {
dispatch(getProjects());
if (currentRole) {
dispatch(getAuditsRequest(currentRole));
dispatch(getAudits(currentRole));
+ dispatch(getOrganizationAudits());
}
}
}, [currentRole, isAuth()]);
@@ -116,7 +138,7 @@ const AppRoutes = () => {
dispatch(getChatList(currentRole));
dispatch(getUnreadForDifferentRole());
}
- }, [currentRole]);
+ }, [currentRole, isAuth()]);
useEffect(() => {
return () => {
@@ -158,6 +180,7 @@ const AppRoutes = () => {
} />
} />
} />
+ } />
} />
} />
} />
@@ -318,6 +341,33 @@ const AppRoutes = () => {
}
/>
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
{/*Add new routes here*/}
} />