From 159ee6648882d5971dc39e2be6713cdbb5725f21 Mon Sep 17 00:00:00 2001 From: Nikita Mashchenko Date: Sat, 24 Jun 2023 23:49:36 -0500 Subject: [PATCH 01/11] Initial websocket refactoring --- client/src/App.js | 3 + .../src/api/hooks/auth/useValidateUsername.js | 2 - client/src/api/hooks/socket/useLoadSocket.js | 67 +++++++++++++++ .../src/api/sockets/notifications.socket.js | 6 +- client/src/components/NavBar/NavBar.jsx | 82 +++++++++---------- .../NotificationsContent.js | 7 +- .../components/UserCard/UserCard.styles.js | 50 +++-------- client/src/store/reducers/UserAuth.js | 8 ++ 8 files changed, 140 insertions(+), 85 deletions(-) create mode 100644 client/src/api/hooks/socket/useLoadSocket.js diff --git a/client/src/App.js b/client/src/App.js index 55ee4c24a..0985abb04 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -4,11 +4,14 @@ import { Toaster } from 'react-hot-toast' import { BrowserRouter as Router } from 'react-router-dom' import { GoogleOAuthProvider } from '@react-oauth/google' +import { useLoadSocket } from './api/hooks/socket/useLoadSocket' import { useRoutes } from './routes/routes' function App() { const routes = useRoutes() + useLoadSocket() + return ( <> diff --git a/client/src/api/hooks/auth/useValidateUsername.js b/client/src/api/hooks/auth/useValidateUsername.js index b96d27e68..21d4a6fc8 100644 --- a/client/src/api/hooks/auth/useValidateUsername.js +++ b/client/src/api/hooks/auth/useValidateUsername.js @@ -7,8 +7,6 @@ import { successToaster } from '../../../shared/components/Toasters/Success.toas const { api } = http export const useValidateUsername = () => { - const queryClient = useQueryClient() - const validateUsername = async (username) => { return await api.get(`/users/get-by-username/${username}`) } diff --git a/client/src/api/hooks/socket/useLoadSocket.js b/client/src/api/hooks/socket/useLoadSocket.js new file mode 100644 index 000000000..692a919f0 --- /dev/null +++ b/client/src/api/hooks/socket/useLoadSocket.js @@ -0,0 +1,67 @@ +import { useEffect } from 'react' +import { useDispatch, useSelector } from 'react-redux' + +import { userAuth } from '../../../store/reducers/UserAuth' +import { socket } from '../../sockets/notifications.socket' + +export const useLoadSocket = (user) => { + const { notifications } = useSelector((state) => state.userReducer) + const dispatch = useDispatch() + + useEffect(() => { + socket.connect() + + function onConnect() { + console.log('connected') + // setIsConnected(true) + socket.emit('subscribeToNotifications', JSON.stringify({ id: '649412b18eab3e1f5587d7bf' })) + } + + function onDisconnect() { + console.log('disconnecting...') + } + + socket.on('connect', onConnect) + socket.on('disconnect', onDisconnect) + + return () => { + socket.disconnect() + + socket.off('connect', onConnect) + socket.off('disconnect', onDisconnect) + // socket.off(`notification-649412b18eab3e1f5587d7bf`, onNotificationsEvent) + } + }, []) + + useEffect(() => { + function onNotificationsEvent(notification) { + console.log(notification) + // Find the index of the existing notification with the same _id + const existingIndex = notifications.findIndex( + (n) => String(n._id) === String(notification._id), + ) + + console.log(existingIndex) + // If an existing notification is found, update it + if (existingIndex !== -1) { + const updatedNotifications = [...notifications] + + console.log(updatedNotifications) + + updatedNotifications[existingIndex] = notification + dispatch(userAuth.actions.setUserNotifications(updatedNotifications)) + } + // If not, add the new notification to the array + else { + console.log([...notifications, notification]) + dispatch(userAuth.actions.setUserNotifications([...notifications, notification])) + } + } + + socket.on(`notification-649412b18eab3e1f5587d7bf`, onNotificationsEvent) + + return () => { + socket.off(`notification-649412b18eab3e1f5587d7bf`, onNotificationsEvent) + } + }, [notifications]) +} diff --git a/client/src/api/sockets/notifications.socket.js b/client/src/api/sockets/notifications.socket.js index 57faa4425..497405846 100644 --- a/client/src/api/sockets/notifications.socket.js +++ b/client/src/api/sockets/notifications.socket.js @@ -2,4 +2,8 @@ import { io } from 'socket.io-client' import { LOCAL_PATH } from '../../http' -export const socket = io(LOCAL_PATH) +export const socket = io(LOCAL_PATH, { + autoConnect: false, +}) + +console.log(socket) diff --git a/client/src/components/NavBar/NavBar.jsx b/client/src/components/NavBar/NavBar.jsx index ac380d859..8e4b87280 100644 --- a/client/src/components/NavBar/NavBar.jsx +++ b/client/src/components/NavBar/NavBar.jsx @@ -56,47 +56,47 @@ const NavBar = () => { const navigate = useNavigate() const navMenuRef = useRef(null) - useEffect(() => { - if (user) { - socket.connect() - - setUserNotifications(user?.notifications) - - console.log(`Connecting socket...`) - socket.emit('subscribeToNotifications', JSON.stringify({ id: user._id })) - - return () => { - socket.disconnect() - } - } - }, [isUserDataLoading]) - - useEffect(() => { - if (user) { - socket.on(`notification-${user._id}`, (notification) => { - // Find the index of the existing notification with the same _id - const existingIndex = userNotifications.findIndex( - (n) => String(n._id) === String(notification._id), - ) - - // If an existing notification is found, update it - if (existingIndex !== -1) { - const updatedNotifications = [...userNotifications] - - updatedNotifications[existingIndex] = notification - setUserNotifications(updatedNotifications) - } - // If not, add the new notification to the array - else { - setUserNotifications([...userNotifications, notification]) - } - }) - - return () => { - socket.off(`notification-${user._id}`) - } - } - }, [userNotifications]) + // useEffect(() => { + // if (user) { + // socket.connect() + + // setUserNotifications(user?.notifications) + + // console.log(`Connecting socket...`) + // socket.emit('subscribeToNotifications', JSON.stringify({ id: user._id })) + + // return () => { + // socket.disconnect() + // } + // } + // }, [isUserDataLoading]) + + // useEffect(() => { + // if (user) { + // socket.on(`notification-${user._id}`, (notification) => { + // // Find the index of the existing notification with the same _id + // const existingIndex = userNotifications.findIndex( + // (n) => String(n._id) === String(notification._id), + // ) + + // // If an existing notification is found, update it + // if (existingIndex !== -1) { + // const updatedNotifications = [...userNotifications] + + // updatedNotifications[existingIndex] = notification + // setUserNotifications(updatedNotifications) + // } + // // If not, add the new notification to the array + // else { + // setUserNotifications([...userNotifications, notification]) + // } + // }) + + // return () => { + // socket.off(`notification-${user._id}`) + // } + // } + // }, [userNotifications]) useOutsideClick(navMenuRef, () => setSidebar(false), notificationModal) diff --git a/client/src/components/NavBar/NotificationsContent/NotificationsContent.js b/client/src/components/NavBar/NotificationsContent/NotificationsContent.js index 991d15c5f..978417989 100644 --- a/client/src/components/NavBar/NotificationsContent/NotificationsContent.js +++ b/client/src/components/NavBar/NotificationsContent/NotificationsContent.js @@ -1,5 +1,5 @@ // assets -import { memo, useEffect } from 'react' +import { useSelector } from 'react-redux' import Notification from '../../../assets/Sidebar/Notification' import IconWrapper from '../../../shared/components/IconWrapper/IconWrapper' @@ -10,11 +10,14 @@ import NotificationsModal from '../NotificationsModal/NotificationsModal' import { NotificationsCount, StyledNotificationsContent } from './NotificationsContent.styles' const NotificationsContent = ({ - userNotifications, + // userNotifications, sidebar, setNotificationModal, notificationModal, }) => { + const { notifications: userNotifications } = useSelector((state) => state.userReducer) + + console.log(userNotifications) const unreadMessages = userNotifications.filter((item) => !item.read) return ( diff --git a/client/src/components/Teammates/components/UserCard/UserCard.styles.js b/client/src/components/Teammates/components/UserCard/UserCard.styles.js index ceaaf3f3e..4be4dd6df 100644 --- a/client/src/components/Teammates/components/UserCard/UserCard.styles.js +++ b/client/src/components/Teammates/components/UserCard/UserCard.styles.js @@ -129,46 +129,18 @@ export const CardContainer = styled.div` background: #1a1c22; border-radius: 15px; - &:hover { + :hover { + border-radius: 15px; + background: linear-gradient( + 146deg, + rgba(184, 197, 229, 0.16) 0%, + rgba(188, 202, 235, 0.08) 100% + ); + + /* shadow 2 */ + box-shadow: 0px 8px 24px 0px rgba(17, 20, 27, 0.2); cursor: pointer; - transition: 0.3s ease-in-out; - -webkit-transform: scale(1.02); - -ms-transform: scale(1.02); - transform: scale(1.02); - } - - &:hover ${Framework}:last-child h3 { - transition: 0.1s ease-in-out; - opacity: ${(props) => (props.ufLength ? 0.2 : 1)}; - } - - &:hover ${LanguageContainer} { - background: rgba(46, 50, 57, 0.35); - transition: 0.1s ease-in-out; - } - - &:hover ${LanguageContainer} svg { - background: transparent; - transition: 0.1s ease-in-out; - } - - &:hover ${LanguageContainer}:last-child svg { - opacity: ${(props) => (props.plLength ? 0.2 : 1)}; - transition: 0.1s ease-in-out; - } - - &:hover ${AndMore} { - transition: 0.1s ease-in-out; - opacity: 1; - background-color: rgba(46, 50, 57, 0.35); - z-index: 999; - } - - &:hover + ${CrownContainer} { - transition: 0.3s ease-in-out; - -webkit-transform: scale(1.1); - -ms-transform: scale(1.1); - transform: scale(1.1); + transition: all 0.2s ease; } ` diff --git a/client/src/store/reducers/UserAuth.js b/client/src/store/reducers/UserAuth.js index 49a39cca7..0be2640e2 100644 --- a/client/src/store/reducers/UserAuth.js +++ b/client/src/store/reducers/UserAuth.js @@ -3,6 +3,8 @@ import { createSlice } from '@reduxjs/toolkit' const initialState = { isAuth: false, isRegistered: false, + isConnected: false, + notifications: [], error: '', } @@ -28,6 +30,12 @@ export const userAuth = createSlice({ authClearError(state) { state.error = '' }, + + setUserNotifications(state, action) { + console.log(action.payload) + state.notifications = action.payload + state.isConnected = true + }, }, }) From 460061dfef02db387359854c641425f9c7bd4436 Mon Sep 17 00:00:00 2001 From: Nikita Mashchenko Date: Sun, 25 Jun 2023 18:01:38 -0500 Subject: [PATCH 02/11] Sockets fix --- client/src/api/hooks/auth/useCheckAuth.js | 2 + client/src/api/hooks/auth/useLoginUser.js | 2 + .../src/api/hooks/sidebar/useReadMessages.js | 10 ++-- client/src/api/hooks/socket/useLoadSocket.js | 51 +++++++++---------- .../src/api/sockets/notifications.socket.js | 2 - client/src/components/NavBar/NavBar.jsx | 45 ---------------- .../NotificationsContent.js | 13 +++-- .../NotificationsList/NotificationsList.js | 8 +-- .../components/UserCard/UserCard.styles.js | 1 + client/src/store/reducers/UserAuth.js | 7 ++- 10 files changed, 49 insertions(+), 92 deletions(-) diff --git a/client/src/api/hooks/auth/useCheckAuth.js b/client/src/api/hooks/auth/useCheckAuth.js index 5a2dfd5b7..7988d203f 100644 --- a/client/src/api/hooks/auth/useCheckAuth.js +++ b/client/src/api/hooks/auth/useCheckAuth.js @@ -20,6 +20,8 @@ export const useCheckAuth = () => { onSuccess: (data) => { if (data && data.isRegistered) { dispatch(userAuth.actions.authUserSuccess()) + dispatch(userAuth.actions.setUserNotifications(data?.notifications)) + dispatch(userAuth.actions.setUserId(data?._id)) } }, onError: (error) => { diff --git a/client/src/api/hooks/auth/useLoginUser.js b/client/src/api/hooks/auth/useLoginUser.js index 61f50d4be..d049a98ba 100644 --- a/client/src/api/hooks/auth/useLoginUser.js +++ b/client/src/api/hooks/auth/useLoginUser.js @@ -36,6 +36,8 @@ export const useLoginUser = (type) => { if (user.isRegistered) { // save accessToken dispatch(userAuth.actions.authUserSuccess()) + dispatch(userAuth.actions.setUserNotifications(user?.notifications)) + dispatch(userAuth.actions.setUserId(data?._id)) navigate('/', { replace: true }) } else { // navigate user to finish registration diff --git a/client/src/api/hooks/sidebar/useReadMessages.js b/client/src/api/hooks/sidebar/useReadMessages.js index cc2490885..5edcc1077 100644 --- a/client/src/api/hooks/sidebar/useReadMessages.js +++ b/client/src/api/hooks/sidebar/useReadMessages.js @@ -1,15 +1,11 @@ -import { useMutation, useQueryClient } from 'react-query' -import { useDispatch } from 'react-redux' +import { useMutation } from 'react-query' import http from '../../../http' -import { registrationAuth } from '../../../store/reducers/RegistrationAuth' +import { errorToaster } from '../../../shared/components/Toasters/Error.toaster' const { api } = http export const useReadMessages = () => { - const dispatch = useDispatch() - const queryClient = useQueryClient() - const readMessages = async (idsArr) => { return await api.put('notifications/read', { notifications: idsArr }) } @@ -21,7 +17,7 @@ export const useReadMessages = () => { }, onError: (error) => { // set error message - dispatch(registrationAuth.actions.finishRegistrationError(error.response?.data?.message)) + errorToaster(error) }, }) } diff --git a/client/src/api/hooks/socket/useLoadSocket.js b/client/src/api/hooks/socket/useLoadSocket.js index 692a919f0..8067a45c6 100644 --- a/client/src/api/hooks/socket/useLoadSocket.js +++ b/client/src/api/hooks/socket/useLoadSocket.js @@ -4,64 +4,61 @@ import { useDispatch, useSelector } from 'react-redux' import { userAuth } from '../../../store/reducers/UserAuth' import { socket } from '../../sockets/notifications.socket' -export const useLoadSocket = (user) => { - const { notifications } = useSelector((state) => state.userReducer) +export const useLoadSocket = () => { + const { notifications, userId } = useSelector((state) => state.userReducer) + const dispatch = useDispatch() - useEffect(() => { - socket.connect() + function onConnect() { + console.log('connected') + // setIsConnected(true) + socket.emit('subscribeToNotifications', JSON.stringify({ id: userId })) + } - function onConnect() { - console.log('connected') - // setIsConnected(true) - socket.emit('subscribeToNotifications', JSON.stringify({ id: '649412b18eab3e1f5587d7bf' })) - } + function onDisconnect() { + console.log('disconnecting...') + } - function onDisconnect() { - console.log('disconnecting...') - } + useEffect(() => { + if (userId) { + socket.connect() - socket.on('connect', onConnect) - socket.on('disconnect', onDisconnect) + socket.on('connect', onConnect) + socket.on('disconnect', onDisconnect) - return () => { - socket.disconnect() + return () => { + socket.disconnect() - socket.off('connect', onConnect) - socket.off('disconnect', onDisconnect) - // socket.off(`notification-649412b18eab3e1f5587d7bf`, onNotificationsEvent) + socket.off('connect', onConnect) + socket.off('disconnect', onDisconnect) + } } - }, []) + }, [userId]) useEffect(() => { function onNotificationsEvent(notification) { - console.log(notification) // Find the index of the existing notification with the same _id const existingIndex = notifications.findIndex( (n) => String(n._id) === String(notification._id), ) - console.log(existingIndex) // If an existing notification is found, update it if (existingIndex !== -1) { const updatedNotifications = [...notifications] - console.log(updatedNotifications) - updatedNotifications[existingIndex] = notification dispatch(userAuth.actions.setUserNotifications(updatedNotifications)) } // If not, add the new notification to the array else { - console.log([...notifications, notification]) dispatch(userAuth.actions.setUserNotifications([...notifications, notification])) } } - socket.on(`notification-649412b18eab3e1f5587d7bf`, onNotificationsEvent) + socket.on(`notification-${userId}`, onNotificationsEvent) return () => { - socket.off(`notification-649412b18eab3e1f5587d7bf`, onNotificationsEvent) + socket.off(`notification-${userId}`, onNotificationsEvent) } }, [notifications]) } diff --git a/client/src/api/sockets/notifications.socket.js b/client/src/api/sockets/notifications.socket.js index 497405846..493f9d0ba 100644 --- a/client/src/api/sockets/notifications.socket.js +++ b/client/src/api/sockets/notifications.socket.js @@ -5,5 +5,3 @@ import { LOCAL_PATH } from '../../http' export const socket = io(LOCAL_PATH, { autoConnect: false, }) - -console.log(socket) diff --git a/client/src/components/NavBar/NavBar.jsx b/client/src/components/NavBar/NavBar.jsx index 8e4b87280..013515efc 100644 --- a/client/src/components/NavBar/NavBar.jsx +++ b/client/src/components/NavBar/NavBar.jsx @@ -40,8 +40,6 @@ const NavBar = () => { const { isAuth } = useSelector((state) => state.userReducer) const { data: user, isFetching: isUserDataLoading } = useCheckAuth() - const [userNotifications, setUserNotifications] = useState(user?.notifications || []) - const newNavData = [ NavBarData[0], { @@ -56,48 +54,6 @@ const NavBar = () => { const navigate = useNavigate() const navMenuRef = useRef(null) - // useEffect(() => { - // if (user) { - // socket.connect() - - // setUserNotifications(user?.notifications) - - // console.log(`Connecting socket...`) - // socket.emit('subscribeToNotifications', JSON.stringify({ id: user._id })) - - // return () => { - // socket.disconnect() - // } - // } - // }, [isUserDataLoading]) - - // useEffect(() => { - // if (user) { - // socket.on(`notification-${user._id}`, (notification) => { - // // Find the index of the existing notification with the same _id - // const existingIndex = userNotifications.findIndex( - // (n) => String(n._id) === String(notification._id), - // ) - - // // If an existing notification is found, update it - // if (existingIndex !== -1) { - // const updatedNotifications = [...userNotifications] - - // updatedNotifications[existingIndex] = notification - // setUserNotifications(updatedNotifications) - // } - // // If not, add the new notification to the array - // else { - // setUserNotifications([...userNotifications, notification]) - // } - // }) - - // return () => { - // socket.off(`notification-${user._id}`) - // } - // } - // }, [userNotifications]) - useOutsideClick(navMenuRef, () => setSidebar(false), notificationModal) const handleUseLogout = () => { @@ -148,7 +104,6 @@ const NavBar = () => { {isAuth && user && ( { const { notifications: userNotifications } = useSelector((state) => state.userReducer) - console.log(userNotifications) - const unreadMessages = userNotifications.filter((item) => !item.read) + const unreadMessages = userNotifications?.filter((item) => !item.read) return ( @@ -31,22 +30,22 @@ const NotificationsContent = ({

Notifications

- {!!unreadMessages.length && !notificationModal && ( + {!!unreadMessages?.length && !notificationModal && ( <> - {unreadMessages.length} + {unreadMessages?.length} )} diff --git a/client/src/components/NavBar/NotificationsList/NotificationsList.js b/client/src/components/NavBar/NotificationsList/NotificationsList.js index 30e0b8886..59c41efc9 100644 --- a/client/src/components/NavBar/NotificationsList/NotificationsList.js +++ b/client/src/components/NavBar/NotificationsList/NotificationsList.js @@ -7,7 +7,9 @@ import { StyledNotificationsList } from './NotificationsList.styles' const NotificationsList = ({ userNotifications, setUnreadIds, closeNotificationsModal }) => { const listRef = useRef(null) - userNotifications.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) + const mutableNotifications = [...userNotifications] + + mutableNotifications.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) useEffect(() => { const observer = new IntersectionObserver((entries, observer) => { @@ -34,8 +36,8 @@ const NotificationsList = ({ userNotifications, setUnreadIds, closeNotifications return ( - {userNotifications && - userNotifications.map((item) => ( + {mutableNotifications && + mutableNotifications.map((item) => ( Date: Tue, 27 Jun 2023 02:37:00 -0500 Subject: [PATCH 03/11] Rewrote Profile, fixed bugs --- client/src/api/endpoints/registration-auth.js | 55 --- client/src/api/endpoints/submission.js | 36 -- client/src/api/endpoints/team.js | 88 ----- client/src/api/endpoints/tournament.js | 36 -- client/src/api/endpoints/users.js | 39 -- .../hooks/{auth => shared}/useUpdateAvatar.js | 0 .../{auth => user}/useEditUserDetails.js | 0 client/src/api/hooks/user/useGetUserById.js | 20 + .../{auth => user}/useValidateUsername.js | 0 client/src/assets/UserProfile/Cake.js | 43 +++ client/src/assets/UserProfile/Email.js | 22 -- client/src/assets/UserProfile/EmailIcon.js | 12 + client/src/assets/UserProfile/LinkIcon.jsx | 21 ++ client/src/assets/UserProfile/Location.js | 22 -- client/src/assets/UserProfile/LocationIcon.js | 12 + client/src/assets/UserProfile/MessageIcon.js | 27 +- client/src/assets/UserProfile/Star.js | 15 - client/src/assets/UserProfile/StarIcon.js | 12 + .../assets/UserProfile/TeamMembersIcon.jsx | 36 ++ client/src/assets/UserProfile/UserIcon.js | 22 ++ .../src/components/CreateTeam/CreateTeam.js | 2 +- .../Forms/Page404Form/Page404Form.js | 2 +- .../Forms/Page404Form/Page404Form.styles.js | 4 + client/src/components/NavBar/NavBar.data.js | 7 - client/src/components/NavBar/NavBar.jsx | 13 +- .../NavBar/NavItem/NavItem.styles.js | 2 +- client/src/components/Profile/Profile.jsx | 121 ++++++ .../src/components/Profile/Profile.styles.js | 188 ++-------- .../ProfileDetails/ProfileDetails.jsx | 185 --------- .../ProfileDetails/ProfileDetails.styles.js | 0 .../ProfileForm/ProfileForm.styles.js | 21 -- .../ProfileForm/components/Edit/Edit.jsx | 13 - .../components/Edit/Edit.styles.js | 7 - .../Profile/components/ProfileForm/index.jsx | 354 ------------------ .../components/ProfileInfo/ProfileInfo.jsx | 89 +++++ .../ProfileInfo/ProfileInfo.styles.js | 91 +++++ .../components/ResumeInfo/ResumeInfo.jsx | 111 ++++++ .../ResumeInfo/ResumeInfo.styles.js | 63 ++++ .../components/ResumeInfo/TagLink/TagLink.jsx | 16 + .../ResumeInfo/TagLink/TagLink.styles.js | 23 ++ .../InfoForm/UserInfoForm/UserInfoForm.jsx | 2 +- .../components/RegistrationPipeline/index.js | 4 +- .../src/components/Team/TeamForm/TeamForm.js | 2 +- .../components/UserProfile/UserProfile.js | 5 +- .../Tournaments.js} | 9 +- .../Tournaments.styles.js} | 29 +- client/src/constants/routes.js | 3 +- client/src/routes/routes.js | 20 +- .../screens/ProfileScreen/ProfileScreen.js | 6 +- .../screens/TournamentsScreen/Tournaments.js | 8 +- .../components/FlexWrapper/FlexWrapper.js | 1 + client/src/shared/styles/Global.styles.js | 4 + 52 files changed, 807 insertions(+), 1116 deletions(-) delete mode 100644 client/src/api/endpoints/registration-auth.js delete mode 100644 client/src/api/endpoints/submission.js delete mode 100644 client/src/api/endpoints/team.js delete mode 100644 client/src/api/endpoints/tournament.js delete mode 100644 client/src/api/endpoints/users.js rename client/src/api/hooks/{auth => shared}/useUpdateAvatar.js (100%) rename client/src/api/hooks/{auth => user}/useEditUserDetails.js (100%) create mode 100644 client/src/api/hooks/user/useGetUserById.js rename client/src/api/hooks/{auth => user}/useValidateUsername.js (100%) create mode 100644 client/src/assets/UserProfile/Cake.js delete mode 100644 client/src/assets/UserProfile/Email.js create mode 100644 client/src/assets/UserProfile/EmailIcon.js create mode 100644 client/src/assets/UserProfile/LinkIcon.jsx delete mode 100644 client/src/assets/UserProfile/Location.js create mode 100644 client/src/assets/UserProfile/LocationIcon.js delete mode 100644 client/src/assets/UserProfile/Star.js create mode 100644 client/src/assets/UserProfile/StarIcon.js create mode 100644 client/src/assets/UserProfile/TeamMembersIcon.jsx create mode 100644 client/src/assets/UserProfile/UserIcon.js create mode 100644 client/src/components/Profile/Profile.jsx delete mode 100644 client/src/components/Profile/components/ProfileDetails/ProfileDetails.jsx delete mode 100644 client/src/components/Profile/components/ProfileDetails/ProfileDetails.styles.js delete mode 100644 client/src/components/Profile/components/ProfileForm/ProfileForm.styles.js delete mode 100644 client/src/components/Profile/components/ProfileForm/components/Edit/Edit.jsx delete mode 100644 client/src/components/Profile/components/ProfileForm/components/Edit/Edit.styles.js delete mode 100644 client/src/components/Profile/components/ProfileForm/index.jsx create mode 100644 client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx create mode 100644 client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js create mode 100644 client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx create mode 100644 client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js create mode 100644 client/src/components/Profile/components/ResumeInfo/TagLink/TagLink.jsx create mode 100644 client/src/components/Profile/components/ResumeInfo/TagLink/TagLink.styles.js rename client/src/components/{Forms/TournamentsForm/TournamentsForm.js => Tournaments/Tournaments.js} (85%) rename client/src/components/{Forms/TournamentsForm/TournamentsForm.styles.js => Tournaments/Tournaments.styles.js} (80%) diff --git a/client/src/api/endpoints/registration-auth.js b/client/src/api/endpoints/registration-auth.js deleted file mode 100644 index 4deb7c1b4..000000000 --- a/client/src/api/endpoints/registration-auth.js +++ /dev/null @@ -1,55 +0,0 @@ -// * Modules -import axios from 'axios' - -// * API -import http from '../../http' -// * Redux -import { registrationAuth } from '../../store/reducers/RegistrationAuth' - -const { API_URL, api } = http - -const checkRegistration = () => async (dispatch) => { - try { - dispatch(registrationAuth.actions.finishRegistration()) - const response = await axios.get(`${API_URL}/get-user-object`, { withCredentials: true }) - let { isRegistered, email, userUsername } = response.data - - dispatch( - registrationAuth.actions.setUserInitialData({ - email, - isRegistered, - userUsername, - }), - ) - } catch (err) { - dispatch(registrationAuth.actions.finishRegistrationError(err.response?.data?.message)) - } -} - -const finishRegistration = (userData) => async (dispatch) => { - try { - dispatch(registrationAuth.actions.finishRegistration()) - await api.post('/registration-checkout', userData) - dispatch(registrationAuth.actions.finishRegistrationSuccess()) - } catch (err) { - dispatch(registrationAuth.actions.finishRegistrationError(err.response?.data?.message)) - } -} - -const validateUsername = async (username, email) => { - try { - const response = await api.get('/check-username', { params: { username, email } }) - - return response.data - } catch (err) { - console.log(err) - } -} - -const registerAuthApi = Object.freeze({ - finishRegistration, - checkRegistration, - validateUsername, -}) - -export default registerAuthApi diff --git a/client/src/api/endpoints/submission.js b/client/src/api/endpoints/submission.js deleted file mode 100644 index ed4911db7..000000000 --- a/client/src/api/endpoints/submission.js +++ /dev/null @@ -1,36 +0,0 @@ -// * API -import http from '../../http' - -const { api } = http -const t_id = '638bcf732c63139d604db787' - -const makeSubmission = async (s_parts, team_id) => { - try { - const data = await api.post('/make-submission', { s_parts, team_id, t_id }) - - return data - } catch (err) { - console.log(err) - - return err.message - } -} - -const getSubmissions = async () => { - try { - const data = await api.post('/get-submissions', { t_id }) - - return data - } catch (err) { - console.log(err) - - return err.message - } -} - -const submissionApi = Object.freeze({ - makeSubmission, - getSubmissions, -}) - -export default submissionApi diff --git a/client/src/api/endpoints/team.js b/client/src/api/endpoints/team.js deleted file mode 100644 index 6aea094eb..000000000 --- a/client/src/api/endpoints/team.js +++ /dev/null @@ -1,88 +0,0 @@ -// * API -import http from '../../http' - -const { api } = http - -const createTeam = async (teamName, teamCountry, teamMembers) => { - try { - const data = await api.post('/teams/create-team', { teamName, teamCountry, teamMembers }) - - return data - } catch (err) { - console.log(err) - - return err.message - } -} - -const getAllTeams = async () => { - try { - const data = await api.get('/teams') - - return data - } catch (err) { - console.log(err) - - return err.message - } -} - -const getTeamById = async (id) => { - try { - // const data = await api.post('/get-team-byid', { teamId }) - const data = await api.get(`/teams/get-team/${id}`) - - return data - } catch (err) { - console.log(err) - - return err.message - } -} - -const getTeamMembers = async (teamMembers) => { - try { - const data = await api.post('/get-teammembers', { teamMembers }) - - return data - } catch (err) { - console.log(err) - - return err.message - } -} - -const addUserToTeam = async (userId, teamId) => { - // try { - // const data = await api.post('/add-to-team', { teamId, userId }) - // - // return data - // } catch (err) { - // console.log(err) - // - // return err.message - // } -} - -const inviteUserByEmail = async (email, teamId, userId) => { - try { - const data = await api.post('/teams/invite', { email, teamid: teamId, from_user_id: userId }) - - return data - } catch (err) { - console.log(err) - - return err.message - } -} - -const teamsApi = Object.freeze({ - createTeam, - getAllTeams, - getTeamById, - getTeamMembers, - addUserToTeam, - inviteUserByEmail, -}) - -export default teamsApi diff --git a/client/src/api/endpoints/tournament.js b/client/src/api/endpoints/tournament.js deleted file mode 100644 index 6719fdc50..000000000 --- a/client/src/api/endpoints/tournament.js +++ /dev/null @@ -1,36 +0,0 @@ -// * API -import http from '../../http' - -const { api } = http - -const addTeamToTournament = async (team_id, frontend_id, backend_id) => { - try { - const t_id = '638bcf732c63139d604db787' - const data = await api.post('/add-to-tournament', { t_id, team_id, frontend_id, backend_id }) - - return data - } catch (err) { - console.log(err) - - return err.message - } -} - -const checkUserSignedUp = async (userId) => { - try { - const data = await api.post('/check-user-exists-tournament', { userId }) - - return data - } catch (err) { - console.log(err) - - return err.message - } -} - -const tournamentApi = Object.freeze({ - addTeamToTournament, - checkUserSignedUp, -}) - -export default tournamentApi diff --git a/client/src/api/endpoints/users.js b/client/src/api/endpoints/users.js deleted file mode 100644 index b0df6cef6..000000000 --- a/client/src/api/endpoints/users.js +++ /dev/null @@ -1,39 +0,0 @@ -// * API -import qs from 'qs' - -import http from '../../http' -import filteredQueryMaker from '../../utils/filteredQueryMaker' - -const { api } = http -const getUsers = async (page) => { - try { - const data = await api.get('/users', { params: { page } }) - - return data - } catch (err) { - console.log(err) - - return err.message - } -} - -const getUsersFiltered = async (page, countries, roles, programmingLanguages) => { - try { - const filtersQuery = filteredQueryMaker(countries, roles, programmingLanguages) - let queryString = qs.stringify(filtersQuery) - const data = await api.get('/users-filtered', { - params: { page, filtersQuery: queryString }, - }) - - return data - } catch (err) { - return err.message - } -} - -const usersApi = Object.freeze({ - getUsers, - getUsersFiltered, -}) - -export default usersApi diff --git a/client/src/api/hooks/auth/useUpdateAvatar.js b/client/src/api/hooks/shared/useUpdateAvatar.js similarity index 100% rename from client/src/api/hooks/auth/useUpdateAvatar.js rename to client/src/api/hooks/shared/useUpdateAvatar.js diff --git a/client/src/api/hooks/auth/useEditUserDetails.js b/client/src/api/hooks/user/useEditUserDetails.js similarity index 100% rename from client/src/api/hooks/auth/useEditUserDetails.js rename to client/src/api/hooks/user/useEditUserDetails.js diff --git a/client/src/api/hooks/user/useGetUserById.js b/client/src/api/hooks/user/useGetUserById.js new file mode 100644 index 000000000..cf18c21da --- /dev/null +++ b/client/src/api/hooks/user/useGetUserById.js @@ -0,0 +1,20 @@ +import { useMutation, useQuery, useQueryClient } from 'react-query' +import { useNavigate } from 'react-router-dom' + +import http from '../../../http' +import { errorToaster } from '../../../shared/components/Toasters/Error.toaster' +import { successToaster } from '../../../shared/components/Toasters/Success.toaster' + +const { api } = http + +export const useGetUserById = (userid) => { + const navigate = useNavigate() + + const getUserById = async () => { + return await api.get(`/users/get-by-id/${userid}`) + } + + const { data, isLoading, error } = useQuery(['getUserById', userid], getUserById, { retry: 0 }) + + return { data, isLoading, error } +} diff --git a/client/src/api/hooks/auth/useValidateUsername.js b/client/src/api/hooks/user/useValidateUsername.js similarity index 100% rename from client/src/api/hooks/auth/useValidateUsername.js rename to client/src/api/hooks/user/useValidateUsername.js diff --git a/client/src/assets/UserProfile/Cake.js b/client/src/assets/UserProfile/Cake.js new file mode 100644 index 000000000..bd27cfab7 --- /dev/null +++ b/client/src/assets/UserProfile/Cake.js @@ -0,0 +1,43 @@ +function CakeIcon() { + return ( + + + + + + + + ) +} + +export default CakeIcon diff --git a/client/src/assets/UserProfile/Email.js b/client/src/assets/UserProfile/Email.js deleted file mode 100644 index 2d934a3be..000000000 --- a/client/src/assets/UserProfile/Email.js +++ /dev/null @@ -1,22 +0,0 @@ -function Email() { - return ( - - - - - ) -} - -export default Email diff --git a/client/src/assets/UserProfile/EmailIcon.js b/client/src/assets/UserProfile/EmailIcon.js new file mode 100644 index 000000000..c0f8cf94a --- /dev/null +++ b/client/src/assets/UserProfile/EmailIcon.js @@ -0,0 +1,12 @@ +function EmailIcon() { + return ( + + + + ) +} + +export default EmailIcon diff --git a/client/src/assets/UserProfile/LinkIcon.jsx b/client/src/assets/UserProfile/LinkIcon.jsx new file mode 100644 index 000000000..36e50b227 --- /dev/null +++ b/client/src/assets/UserProfile/LinkIcon.jsx @@ -0,0 +1,21 @@ +function LinkIcon() { + return ( + + + + + + ) +} + +export default LinkIcon diff --git a/client/src/assets/UserProfile/Location.js b/client/src/assets/UserProfile/Location.js deleted file mode 100644 index 582fb3834..000000000 --- a/client/src/assets/UserProfile/Location.js +++ /dev/null @@ -1,22 +0,0 @@ -function Location() { - return ( - - - - - ) -} - -export default Location diff --git a/client/src/assets/UserProfile/LocationIcon.js b/client/src/assets/UserProfile/LocationIcon.js new file mode 100644 index 000000000..48b31aba4 --- /dev/null +++ b/client/src/assets/UserProfile/LocationIcon.js @@ -0,0 +1,12 @@ +function LocationIcon() { + return ( + + + + ) +} + +export default LocationIcon diff --git a/client/src/assets/UserProfile/MessageIcon.js b/client/src/assets/UserProfile/MessageIcon.js index 573c32902..467fb67a2 100644 --- a/client/src/assets/UserProfile/MessageIcon.js +++ b/client/src/assets/UserProfile/MessageIcon.js @@ -1,20 +1,23 @@ function MessageIcon() { return ( - + + + ) diff --git a/client/src/assets/UserProfile/Star.js b/client/src/assets/UserProfile/Star.js deleted file mode 100644 index d7c3fb35d..000000000 --- a/client/src/assets/UserProfile/Star.js +++ /dev/null @@ -1,15 +0,0 @@ -function Star() { - return ( - - - - ) -} - -export default Star diff --git a/client/src/assets/UserProfile/StarIcon.js b/client/src/assets/UserProfile/StarIcon.js new file mode 100644 index 000000000..8fc83c63d --- /dev/null +++ b/client/src/assets/UserProfile/StarIcon.js @@ -0,0 +1,12 @@ +function StarIcon() { + return ( + + + + ) +} + +export default StarIcon diff --git a/client/src/assets/UserProfile/TeamMembersIcon.jsx b/client/src/assets/UserProfile/TeamMembersIcon.jsx new file mode 100644 index 000000000..9f93a8771 --- /dev/null +++ b/client/src/assets/UserProfile/TeamMembersIcon.jsx @@ -0,0 +1,36 @@ +function TeamMembersIcon() { + return ( + + + + + + + ) +} + +export default TeamMembersIcon diff --git a/client/src/assets/UserProfile/UserIcon.js b/client/src/assets/UserProfile/UserIcon.js new file mode 100644 index 000000000..9f7ca0140 --- /dev/null +++ b/client/src/assets/UserProfile/UserIcon.js @@ -0,0 +1,22 @@ +function UserIcon() { + return ( + + + + + ) +} + +export default UserIcon diff --git a/client/src/components/CreateTeam/CreateTeam.js b/client/src/components/CreateTeam/CreateTeam.js index 3a888f75a..67715b8af 100644 --- a/client/src/components/CreateTeam/CreateTeam.js +++ b/client/src/components/CreateTeam/CreateTeam.js @@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom' // API import { useCheckAuth } from '../../api/hooks/auth/useCheckAuth' -import { useUpdateAvatar } from '../../api/hooks/auth/useUpdateAvatar' +import { useUpdateAvatar } from '../../api/hooks/shared/useUpdateAvatar' import { useCreateTeam } from '../../api/hooks/team/useCreateTeam' import { defaultTeamAvatars } from '../../constants/teamFormData' // * Assets diff --git a/client/src/components/Forms/Page404Form/Page404Form.js b/client/src/components/Forms/Page404Form/Page404Form.js index 35e7d729a..6057b1719 100644 --- a/client/src/components/Forms/Page404Form/Page404Form.js +++ b/client/src/components/Forms/Page404Form/Page404Form.js @@ -28,7 +28,7 @@ const Page404Form = ({ paddingLeft = '0', findText = `Couldn't find the requeste {findText} - + diff --git a/client/src/components/Forms/Page404Form/Page404Form.styles.js b/client/src/components/Forms/Page404Form/Page404Form.styles.js index 49a800838..fbf3e25fd 100644 --- a/client/src/components/Forms/Page404Form/Page404Form.styles.js +++ b/client/src/components/Forms/Page404Form/Page404Form.styles.js @@ -8,6 +8,10 @@ export const Container = styled.div` align-items: center; justify-content: center; padding-left: ${(props) => props.paddingLeft || '0'}; + + @media screen and (max-width: 768px) { + padding-left: 0; + } ` export const InfoContainer = styled.div` display: flex; diff --git a/client/src/components/NavBar/NavBar.data.js b/client/src/components/NavBar/NavBar.data.js index c6a21e2ab..5a3646688 100644 --- a/client/src/components/NavBar/NavBar.data.js +++ b/client/src/components/NavBar/NavBar.data.js @@ -3,9 +3,7 @@ import React from 'react' // * Assets import SearchIcon from '../../assets/Sidebar/SearchIcon' -// import Team from '../../assets/Sidebar/Team' unused import TrophyIcon from '../../assets/Sidebar/TrophyIcon' -import UserIcon from '../../assets/Sidebar/UserIcon' export const NavBarData = [ { @@ -18,9 +16,4 @@ export const NavBarData = [ path: '/tournaments', icon: , }, - { - title: 'Profile', - path: '/profile', - icon: , - }, ] diff --git a/client/src/components/NavBar/NavBar.jsx b/client/src/components/NavBar/NavBar.jsx index 013515efc..4138cd38f 100644 --- a/client/src/components/NavBar/NavBar.jsx +++ b/client/src/components/NavBar/NavBar.jsx @@ -5,12 +5,12 @@ import { useNavigate } from 'react-router-dom' import { useCheckAuth } from '../../api/hooks/auth/useCheckAuth' import { useLogoutUser } from '../../api/hooks/auth/useLogoutUser' -import { socket } from '../../api/sockets/notifications.socket' // * Assets import Close from '../../assets/Sidebar/Close' import Exit from '../../assets/Sidebar/Exit' import ShortLogo from '../../assets/Sidebar/ShortLogo' import Team from '../../assets/Sidebar/Team' +import UserIcon from '../../assets/Sidebar/UserIcon' import { useOutsideClick } from '../../hooks/useOutsideClick' import IconWrapper from '../../shared/components/IconWrapper/IconWrapper' import Loader from '../../shared/components/Loader/Loader' @@ -38,7 +38,7 @@ const NavBar = () => { const [notificationModal, setNotificationModal] = useState(false) const { isAuth } = useSelector((state) => state.userReducer) - const { data: user, isFetching: isUserDataLoading } = useCheckAuth() + const { data: user } = useCheckAuth() const newNavData = [ NavBarData[0], @@ -47,8 +47,13 @@ const NavBar = () => { icon: , path: user?.team ? `/team/${user.team._id}` : '/team', }, - ...NavBarData.slice(1), - ] + NavBarData[1], + user && { + title: 'Profile', + path: `/profile/${user._id}`, + icon: , + }, + ].filter(Boolean) const { mutate: logoutUser, isLoading: isUserLoggingOut } = useLogoutUser() const navigate = useNavigate() diff --git a/client/src/components/NavBar/NavItem/NavItem.styles.js b/client/src/components/NavBar/NavItem/NavItem.styles.js index 85f4a1837..535f77b6a 100644 --- a/client/src/components/NavBar/NavItem/NavItem.styles.js +++ b/client/src/components/NavBar/NavItem/NavItem.styles.js @@ -10,7 +10,7 @@ export const StyledLink = styled(NavLink)` text-decoration: none; color: #fff; padding: 8px 16px; - transition: background-color 0.5s; + /* transition: background-color 0.5s; */ display: flex; align-items: center; gap: 8px; diff --git a/client/src/components/Profile/Profile.jsx b/client/src/components/Profile/Profile.jsx new file mode 100644 index 000000000..98a4bab52 --- /dev/null +++ b/client/src/components/Profile/Profile.jsx @@ -0,0 +1,121 @@ +import { useNavigate, useParams } from 'react-router-dom' +import { Form, Formik } from 'formik' + +import { useCheckAuth } from '../../api/hooks/auth/useCheckAuth' +import { useEditUserDetails } from '../../api/hooks/user/useEditUserDetails' +import { useGetUserById } from '../../api/hooks/user/useGetUserById' +import PlatformLogo from '../../assets/Platform/TeameightsLogo' +import ROUTES from '../../constants/routes' +import { usePrompt } from '../../hooks/usePrompt' +import { LOCAL_PATH } from '../../http' +import { editProfileValidation } from '../../schemas' +import { LogoContainer } from '../../shared/components/AppHeader/AppHeader.styles' +import CustomButton from '../../shared/components/CustomButton/CustomButton' +import CustomInput from '../../shared/components/Formik/CustomInput/CustomInput' +import CustomSelect from '../../shared/components/Formik/CustomSelect/CustomSelect' +import CustomTextArea from '../../shared/components/Formik/CustomTextArea/CustomTextArea' +import Loader from '../../shared/components/Loader/Loader' +import { Button } from '../../shared/styles/Button.styles' +import { ErrorMessage } from '../../shared/styles/Tpography.styles' +import { calculateAge } from '../../utils/calculateAge' +import Page404Form from '../Forms/Page404Form/Page404Form' + +import ProfileInfo from './components/ProfileInfo/ProfileInfo' +import ResumeInfo from './components/ResumeInfo/ResumeInfo' +import { ProfileContainer, ProfileWrapper } from './Profile.styles' + +const Profile = () => { + const navigate = useNavigate() + const { id } = useParams() + const { mutate: editUserDetails, isLoading } = useEditUserDetails() + const { data, isLoading: isUserLoading, error } = useGetUserById(id) + + const user = data?.data + + const teamSearchHandler = () => { + navigate('/teams') + } + + const handleSubmit = (values) => { + const { + fullName, + description, + concentration, + country, + experience, + github, + telegram, + linkedIn, + programmingLanguages, + frameworks, + } = values + const modifiedUserData = { + email: user.email, + fullName, + description, + concentration, + country, + experience, + links: { + github, + telegram, + linkedIn, + }, + programmingLanguages, + frameworks, + } + + editUserDetails(modifiedUserData) + } + + if (isLoading || isUserLoading) { + return + } + + if ((!isUserLoading && !user) || error) { + return + } + + /* Check if current userId is the same as passed in params */ + + /* If not, check if current user has team */ + + return ( + + {({ values, errors, dirty }) => { + usePrompt('You have unsaved changes. Do you want to discard them?', dirty) + + return ( +
+ + {/* + + */} + + + + + +
+ ) + }} +
+ ) +} + +export default Profile diff --git a/client/src/components/Profile/Profile.styles.js b/client/src/components/Profile/Profile.styles.js index 32b0764be..e890e2a11 100644 --- a/client/src/components/Profile/Profile.styles.js +++ b/client/src/components/Profile/Profile.styles.js @@ -1,178 +1,46 @@ -import { Telegram } from '@mui/icons-material' import styled from 'styled-components' -import { BLACK, WHITE } from '../../constants/colors' - -export const Container = styled.div` +export const ProfileWrapper = styled.div` display: flex; flex-direction: column; justify-content: center; align-items: center; - min-height: 100vh; - width: 100%; - background: ${BLACK.background}; + min-height: 100dvh; + padding-left: 88px; + background: #26292b; + + @media (max-width: 768px) { + padding-left: 0; + } ` -export const LeftCard = styled.div` - min-height: 565px; - width: 370px; - background: #1a1c22; - box-shadow: 0px 4px 25px rgba(0, 0, 0, 0.25); - border-radius: 15px; +export const ProfileContainer = styled.div` display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -` + max-width: 800px; + gap: 30px; + margin: 0 auto; + padding: 0 15px; -export const TelegramIcon = styled(Telegram)` - color: #fff; - width: 1.25rem !important; - height: 1.25rem !important; + @media (max-width: 1024px) { + flex-direction: column; + align-items: center; + margin: 96px 0 24px 0; + } ` -export const RightContainer = styled.div` - width: 470px; +export const ProfileSection = styled.div` display: flex; - border-radius: 15px; - justify-content: space-between; flex-direction: column; - gap: 10px; -` - -export const RightCard = styled.div` - width: 470px; - min-height: 125px; + align-items: ${(props) => props.align || 'normal'}; + gap: ${(props) => props.gap || '0'}; + width: ${(props) => props.width || 'auto'}; + height: 600px; background: #1a1c22; - box-shadow: 0px 4px 25px rgba(0, 0, 0, 0.25); border-radius: 15px; - display: flex; - justify-content: start; - align-items: start; - flex-direction: column; - padding: 10px 10px; -` - -export const TextContainer = styled.div` - display: flex; - justify-content: center; - flex-direction: column; - align-items: justify; - margin-bottom: 10px; -` - -export const Text = styled.h3` - font-weight: ${(props) => props.fontWeight || '500'}; - font-size: ${(props) => props.fontSize || '18px'}; - margin: ${(props) => props.margin || '0'}; - color: ${(props) => props.color || WHITE.main}; - text-align: ${(props) => props.alignment || 'center'}; -` - -export const ImgContainer = styled.div` - position: relative; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; -` - -export const Img = styled.img` - width: 150px; - height: 150px; - border-radius: 50%; - margin-top: 30px; -` - -export const ProfileLine = styled.hr` - border: 1px solid rgba(42, 60, 19, 0.5); - width: 90%; -` - -export const BannerLine = styled.hr` - width: 450px; - height: 0px; - border: 1px solid rgba(42, 60, 19, 0.5); -` - -export const InformationRow = styled.div` - width: 90%; - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 15px; -` - -export const SocialRow = styled.div` - display: flex; - align-items: center; - margin-bottom: 10px; -` - -export const SocialWrapper = styled.div` - margin-top: 30px; - width: 90%; -` - -export const IconTextContainer = styled.div` - display: flex; - gap: 15px; - align-items: center; -` - -export const Cards = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - min-height: 100vh; -` - -export const Information = styled.div` - display: flex; - flex-direction: row; - gap: 50px; - justify-content: center; - width: 100%; -` - -export const RightCardData = styled.div` - display: flex; - width: 100%; - gap: 10px; - flex-wrap: wrap; - min-height: 50px; - height: 100%; - justify-content: ${(props) => props.justify || 'start'}; - align-items: center; -` - -export const ProgrammingLanguage = styled.div` - width: 40px; - height: 40px; - background: #2e3239; - border-radius: 5px; - display: flex; - justify-content: center; - align-items: center; -` - -export const Framework = styled.div` - width: 65px; - height: 35px; - background: #00a4d3; - border-radius: 5px; - display: flex; - justify-content: center; - align-items: center; - background: ${(props) => props.background || '#42443B'}; -` + padding: ${(props) => props.padding || '0'}; -export const DetailsWrapper = styled.div` - display: flex; - flex-direction: column; - width: 90%; -` -export const InformationWrapper = styled.div` - display: flex; - flex-direction: column; + @media (max-width: 1024px) { + max-width: 470px; + width: 100%; + } ` diff --git a/client/src/components/Profile/components/ProfileDetails/ProfileDetails.jsx b/client/src/components/Profile/components/ProfileDetails/ProfileDetails.jsx deleted file mode 100644 index 0f3403ddf..000000000 --- a/client/src/components/Profile/components/ProfileDetails/ProfileDetails.jsx +++ /dev/null @@ -1,185 +0,0 @@ -import React from 'react' -import { useNavigate } from 'react-router-dom' - -import { useCheckAuth } from '../../../../api/hooks/auth/useCheckAuth' -import { useGetTeamData } from '../../../../api/hooks/team/useGetTeamData' -import Email from '../../../../assets/UserProfile/Email' -import Github from '../../../../assets/UserProfile/Github' -import Linkedin from '../../../../assets/UserProfile/Linkedin' -import Location from '../../../../assets/UserProfile/Location' -import Star from '../../../../assets/UserProfile/Star' -import { Framework } from '../../../../components/Teammates/components/UserCard/UserCard.styles' -import { frameworkColors, frameworkTextColors } from '../../../../constants/frameworkColors' -import { languageOptions } from '../../../../constants/programmingLanguages' -import ROUTES from '../../../../constants/routes' -import { LOCAL_PATH } from '../../../../http' -import AvatarEditButton from '../../../../shared/components/Forms/UserAvatar/AvatarEditButton/AvatarEditButton' -import { UserAvatar } from '../../../../shared/components/Forms/UserAvatar/UserAvatar.styles' -import Loader from '../../../../shared/components/Loader/Loader' -import ModalWindow from '../../../../shared/components/ModalWindow/ModalWindow' -import { Button } from '../../../../shared/styles/Button.styles' -import { CustomLink } from '../../../../shared/styles/Link.styles' -import { AvatarWrapper } from '../../../RegistrationPipeline/components/RegistrationForms/AvatarForm/AvatarForm.styles' -import { - BannerLine, - DetailsWrapper, - IconTextContainer, - Img, - ImgContainer, - Information, - InformationRow, - InformationWrapper, - LeftCard, - ProfileLine, - ProgrammingLanguage, - RightCard, - RightCardData, - RightContainer, - SocialRow, - SocialWrapper, - TelegramIcon, - Text, - TextContainer, -} from '../../Profile.styles' - -const ProfileDetails = () => { - const { data: user, isFetching: isUserDataLoading } = useCheckAuth() - - const navigate = useNavigate() - - if (isUserDataLoading) { - return - } - - if (!user?.isRegistered) { - return - } - - return ( - - - - - navigate('/profile-edit')} /> - - - {user.fullName} - - {user.username} - - {user.concentration} - - - - - - - - {user.country} - - - - - - {user.experience} years of experiences - - - - - - {user.email} - - - - - {user.links?.github && ( - - - - - Github - - - - )} - {user.links?.linkedIn && ( - - - - - Linkedin - - - - )} - {user.links?.telegram && ( - - - - - Telegram - - - - )} - - - - - - - Languages - - - - {user.programmingLanguages.map((language) => ( - {languageOptions[language]} - ))} - - - - - Tools - - - - {user.frameworks.slice(0, 5).map((framework, index) => ( - -

{index < 4 ? framework : `+${user.frameworks.length - 4}`}

- - ))} - - - - - Team - - - - - {user.team ? user?.team.name : "That's where your team will come in"} - - - - - - About me - - - - - {user.description ? user.description : 'This user is humble'} - - - - - - ) -} - -export default ProfileDetails diff --git a/client/src/components/Profile/components/ProfileDetails/ProfileDetails.styles.js b/client/src/components/Profile/components/ProfileDetails/ProfileDetails.styles.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/components/Profile/components/ProfileForm/ProfileForm.styles.js b/client/src/components/Profile/components/ProfileForm/ProfileForm.styles.js deleted file mode 100644 index be6dd678f..000000000 --- a/client/src/components/Profile/components/ProfileForm/ProfileForm.styles.js +++ /dev/null @@ -1,21 +0,0 @@ -import styled from 'styled-components' - -export const EditUserDetails = styled.div` - display: flex; - justify-content: center; - align-items: center; - position: absolute; - bottom: 0.5rem; - right: 1rem; - border-radius: 50%; - width: 1.875rem; - height: 1.875rem; - background: #5d9d0b; - cursor: pointer; -` - -export const ConcentrationWrapper = styled.div` - display: flex; - align-items: center; - justify-content: space-between; -` diff --git a/client/src/components/Profile/components/ProfileForm/components/Edit/Edit.jsx b/client/src/components/Profile/components/ProfileForm/components/Edit/Edit.jsx deleted file mode 100644 index 373e7aa5c..000000000 --- a/client/src/components/Profile/components/ProfileForm/components/Edit/Edit.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import PlusIcon from '../../../../../../assets/UserProfile/PlusIcon' - -import { EditButton } from './Edit.styles' - -const Edit = ({ className }) => { - return ( - - - - ) -} - -export default Edit diff --git a/client/src/components/Profile/components/ProfileForm/components/Edit/Edit.styles.js b/client/src/components/Profile/components/ProfileForm/components/Edit/Edit.styles.js deleted file mode 100644 index 91a4b6e82..000000000 --- a/client/src/components/Profile/components/ProfileForm/components/Edit/Edit.styles.js +++ /dev/null @@ -1,7 +0,0 @@ -import styled from 'styled-components' - -export const EditButton = styled.div` - cursor: pointer; - width: 1.125rem; - height: 1.125rem; -` diff --git a/client/src/components/Profile/components/ProfileForm/index.jsx b/client/src/components/Profile/components/ProfileForm/index.jsx deleted file mode 100644 index deeb9f783..000000000 --- a/client/src/components/Profile/components/ProfileForm/index.jsx +++ /dev/null @@ -1,354 +0,0 @@ -import { useMemo } from 'react' -import { useNavigate } from 'react-router-dom' -import countryList from 'react-select-country-list' -import { Form, Formik } from 'formik' - -import { useCheckAuth } from '../../../../api/hooks/auth/useCheckAuth' -import { useEditUserDetails } from '../../../../api/hooks/auth/useEditUserDetails' -import { useGetTeamData } from '../../../../api/hooks/team/useGetTeamData' -import EditIcon from '../../../../assets/Shared/EditIcon' -import Email from '../../../../assets/UserProfile/Email' -import Github from '../../../../assets/UserProfile/Github' -import Linkedin from '../../../../assets/UserProfile/Linkedin' -import Location from '../../../../assets/UserProfile/Location' -import Star from '../../../../assets/UserProfile/Star' -import { Framework } from '../../../../components/Teammates/components/UserCard/UserCard.styles' -import concentrationOptions from '../../../../constants/concentrations' -import { userExperienceOptions } from '../../../../constants/finishRegistrationData' -import { frameworkColors, frameworkTextColors } from '../../../../constants/frameworkColors' -import frameworkOptions from '../../../../constants/frameworks' -import { - languageOptions, - programmingLanguageOptions, -} from '../../../../constants/programmingLanguages' -import ROUTES from '../../../../constants/routes' -import { usePrompt } from '../../../../hooks/usePrompt' -import { LOCAL_PATH } from '../../../../http' -import { editProfileValidation } from '../../../../schemas' -import CustomButton from '../../../../shared/components/CustomButton/CustomButton' -import CustomInput from '../../../../shared/components/Formik/CustomInput/CustomInput' -import CustomSelect from '../../../../shared/components/Formik/CustomSelect/CustomSelect' -import CustomTextArea from '../../../../shared/components/Formik/CustomTextArea/CustomTextArea' -import Loader from '../../../../shared/components/Loader/Loader' -import { Button } from '../../../../shared/styles/Button.styles' -import { ErrorMessage } from '../../../../shared/styles/Tpography.styles' -import { - BannerLine, - DetailsWrapper, - IconTextContainer, - Img, - ImgContainer, - Information, - InformationRow, - InformationWrapper, - LeftCard, - ProfileLine, - ProgrammingLanguage, - RightCard, - RightCardData, - RightContainer, - SocialRow, - SocialWrapper, - TelegramIcon, - Text, - TextContainer, -} from '../../Profile.styles' - -import Edit from './components/Edit/Edit' -import { ConcentrationWrapper, EditUserDetails } from './ProfileForm.styles' - -const inputStyles = { - border: '1px solid #5E5E5E', - borderRadius: '0.5rem', - padding: '0.5rem', - width: 'auto', -} - -const ProfileForm = () => { - const navigate = useNavigate() - const { mutate: editUserDetails, isLoading } = useEditUserDetails(onSuccess) - const { data: user, isFetching: isUserDataLoading } = useCheckAuth() - const teamId = user?.team?._id - - const { data: team, isLoading: isUserTeamLoading } = useGetTeamData(teamId) - const countriesOptions = useMemo(() => countryList().getData(), []) - - const stopEditing = () => navigate('/profile') - - function onSuccess() { - stopEditing() - } - - const teamSearchHandler = () => { - navigate('/teams') - } - - const handleSubmit = (values) => { - const { - fullName, - description, - concentration, - country, - experience, - github, - telegram, - linkedIn, - programmingLanguages, - frameworks, - } = values - const modifiedUserData = { - email: user.email, - fullName, - description, - concentration, - country, - experience, - links: { - github, - telegram, - linkedIn, - }, - programmingLanguages, - frameworks, - } - - editUserDetails(modifiedUserData) - } - - if (isLoading || isUserDataLoading || isUserTeamLoading) { - return - } - - if (!user) { - return - } - - return ( - - {({ values, errors, dirty }) => { - usePrompt('You have unsaved changes. Do you want to discard them?', dirty) - - return ( -
- - - - - - - - - - - - {user.userUsername} - - - {values.concentration} - <>} - width="auto" - margin="0" - name="concentration" - options={concentrationOptions} - line={false} - IconComponent={Edit} - /> - - - - - - - - - {values.country} - - <>} - width="auto" - margin="0 0 0 1rem" - name="country" - options={countriesOptions} - line={false} - IconComponent={Edit} - /> - - - - - - {values.experience} {!values.experience.includes('years') ? ' years' : ''}{' '} - of experiences - - - <>} - width="auto" - margin="0 0 0 1rem" - name="experience" - options={userExperienceOptions} - line={false} - IconComponent={Edit} - /> - - - - - {user.email} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Languages - - - - {values.programmingLanguages.map((language) => ( - - {languageOptions[language]} - - ))} - <>} - width="auto" - name="programmingLanguages" - margin="0" - multiple={true} - options={programmingLanguageOptions} - line={false} - IconComponent={Edit} - /> - {errors.programmingLanguages && ( - - {errors.programmingLanguages} - - )} - - - - - Tools - - - - {values.frameworks.slice(0, 5).map((framework, index) => ( - -

{index < 4 ? framework : `+${values.frameworks.length - 4}`}

-
- ))} - <>} - width="auto" - name="frameworks" - margin="0" - multiple={true} - options={frameworkOptions} - line={false} - IconComponent={Edit} - /> - {errors.frameworks && ( - {errors.frameworks} - )} -
-
- - - - Team - - - - {team ? ( - - {team.name} - - ) : ( - - Search team - - )} - - - - - - About me - - - - - - -
-
- - Save - -
- ) - }} -
- ) -} - -export default ProfileForm diff --git a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx new file mode 100644 index 000000000..01de21da9 --- /dev/null +++ b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx @@ -0,0 +1,89 @@ +import BehanceIcon from '../../../../assets/Links/BehanceIcon' +import GitHubIcon from '../../../../assets/Links/GitHubIcon' +import LinkedInIcon from '../../../../assets/Links/LinkedInIcon' +import TelegramIcon from '../../../../assets/Links/TelegramIcon' +import CakeIcon from '../../../../assets/UserProfile/Cake' +import EmailIcon from '../../../../assets/UserProfile/EmailIcon' +import LocationIcon from '../../../../assets/UserProfile/LocationIcon' +import MessageIcon from '../../../../assets/UserProfile/MessageIcon' +import StarIcon from '../../../../assets/UserProfile/StarIcon' +import UserIcon from '../../../../assets/UserProfile/UserIcon' +import FlexWrapper from '../../../../shared/components/FlexWrapper/FlexWrapper' +import { calculateAge } from '../../../../utils/calculateAge' +import { ProfileSection } from '../../Profile.styles' + +import { + AvatarImg, + AvatarWrapper, + InfoList, + InfoListItem, + MessageBtn, + SocialList, + Text, + UserInfo, +} from './ProfileInfo.styles' + +const ProfileInfo = ({ user }) => { + const infoListArr = [ + { icon: , infoEl: user?.concentration }, + { icon: , infoEl: `${user?.experience} years of experience` }, + { icon: , infoEl: user?.country }, + { icon: , infoEl: `${calculateAge(user?.dateOfBirth)} years old` }, + { + icon: , + infoEl: ( + + {user?.email} + + ), + }, + ] + + const socialArr = [ + user?.links?.github && { icon: , link: user?.links?.github }, + user?.links?.behance && { icon: , link: user?.links?.behance }, + user?.links?.telegram && { icon: , link: user?.links?.telegram }, + user?.links?.linkedIn && { icon: , link: user?.links?.linkedIn }, + ].filter(Boolean) + + return ( + + + + + + + {user?.fullName} + + @{user?.username} + + + + Message + + + + + {infoListArr.map((item, index) => ( + + {item.icon} + + {item.infoEl} + + + ))} + + {socialArr.length > 0 && ( + + {socialArr.map((social, index) => ( +
  • + {social.icon} +
  • + ))} +
    + )} +
    + ) +} + +export default ProfileInfo diff --git a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js new file mode 100644 index 000000000..ad9b7ecbe --- /dev/null +++ b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js @@ -0,0 +1,91 @@ +import styled from 'styled-components' + +export const UserInfo = styled.div` + display: flex; + width: 100%; + flex-direction: column; + align-items: center; + gap: 24px; +` + +export const AvatarWrapper = styled.div` + position: relative; +` + +export const AvatarImg = styled.img` + display: block; + border-radius: 50%; + width: 100px; + height: 100px; +` + +export const Text = styled.p` + font-weight: ${(props) => props.fontWeight || '500'}; + font-size: ${(props) => props.fontSize || '20px'}; + color: ${(props) => props.color || '#fff'}; ; +` + +export const MessageBtn = styled.button` + height: 40px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + width: 100%; + max-width: 222px; + background: transparent; + padding: 10px 0; + border: 2px solid #46a11b; + border-radius: 10px; + transition: background-color 0.3s; + color: #fff; + font-weight: 400; + font-size: 16px; + &:hover { + background-color: rgba(250, 250, 250, 0.05); + } + @media (max-width: 1024px) { + max-width: none; + } +` + +export const InfoList = styled.ul` + display: flex; + flex-direction: column; + gap: 9px; + @media (max-width: 1024px) { + align-self: flex-start; + } + + a { + text-decoration: none; + color: white; + font-size: 16px; + font-weight: 400; + + :hover { + color: #5bd424; + } + } +` + +export const InfoListItem = styled.li` + display: flex; + align-items: center; + gap: 8px; +` + +export const SocialList = styled.ul` + margin-top: auto; + display: flex; + justify-content: center; + gap: 24px; + flex-wrap: wrap; + svg { + transition: opacity 0.3s; + :hover { + opacity: 0.6; + } + } +` diff --git a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx new file mode 100644 index 000000000..dba839b96 --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx @@ -0,0 +1,111 @@ +import { useState } from 'react' + +import LinkIcon from '../../../../assets/UserProfile/LinkIcon' +import TeamMembersIcon from '../../../../assets/UserProfile/TeamMembersIcon' +import { frameworkColors, frameworkTextColors } from '../../../../constants/frameworkColors' +import { languageOptions } from '../../../../constants/programmingLanguages' +import FlexWrapper from '../../../../shared/components/FlexWrapper/FlexWrapper' +import { ProfileSection } from '../../Profile.styles' + +import TagLink from './TagLink/TagLink' +import { + FrameWorkItem, + LanguageItem, + ResumePartBox, + ResumePartBtn, + Text, + WrappableList, +} from './ResumeInfo.styles' + +const ResumeInfo = ({ user }) => { + const projectsArr = [ + { text: 'Team8s', link: '#' }, + { text: 'BankingApp', link: '#' }, + { text: 'Snake', link: '#' }, + { text: 'Shopping App', link: '#' }, + { text: 'E-learning', link: '#' }, + ] + + const [active, setActive] = useState('projects') + + return ( + + + + setActive('projects')}> + Projects & Skills + + setActive('education')}> + Education & Work + + + + + + About me + + + {user?.description ? user?.description : 'This user is humble'} + + + + + Team + + {user?.team ? ( + } to={`/team/${user?.team._id}`}> + {user?.team.name} + + ) : ( + + No team yet. + + )} + + + + Projects + + + {projectsArr.map(({ link, text }, index) => ( +
  • + }> + {text} + +
  • + ))} +
    +
    + + + Frameworks + + + {user?.frameworks?.map((framework, index) => ( + + {framework} + + ))} + + + + + Languages + + + {user?.programmingLanguages?.map((language, index) => ( + {languageOptions[language]} + ))} + + +
    +
    +
    + ) +} + +export default ResumeInfo diff --git a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js new file mode 100644 index 000000000..1185631f2 --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js @@ -0,0 +1,63 @@ +import styled, { css } from 'styled-components' + +export const Text = styled.p` + font-weight: ${(props) => props.fontWeight || '500'}; + font-size: ${(props) => props.fontSize || '20px'}; + color: ${(props) => props.color || '#fff'}; + line-height: '120%'; +` + +export const ResumePartBox = styled.div` + height: 32px; + display: flex; + align-items: center; + gap: 16px; +` + +export const ResumePartBtn = styled.div` + position: relative; + cursor: pointer; + color: ${(props) => (props.isActive ? '#5BD424' : '#fff')}; + transition: opacity 0.3s; + &:hover { + opacity: 0.6; + } + ${(props) => + props.isActive && + css` + &:before { + content: ''; + position: absolute; + left: 0; + bottom: -4px; + height: 1px; + background-color: #5bd424; + width: 100%; + } + `} +` + +export const WrappableList = styled.ul` + display: flex; + flex-wrap: wrap; + gap: ${(props) => props.gap || '0'}; +` + +export const FrameWorkItem = styled.li` + padding: 5px 0; + width: 100px; + text-align: center; + border-radius: 5px; + background: ${(props) => props.background || '#2F3239'}; + color: ${(props) => props.color || '#2F3239'}; +` + +export const LanguageItem = styled.li` + width: 40px; + height: 40px; + background-color: #2f3239; + border-radius: 5px; + display: flex; + justify-content: center; + align-items: center; +` diff --git a/client/src/components/Profile/components/ResumeInfo/TagLink/TagLink.jsx b/client/src/components/Profile/components/ResumeInfo/TagLink/TagLink.jsx new file mode 100644 index 000000000..7b3bd1146 --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/TagLink/TagLink.jsx @@ -0,0 +1,16 @@ +import { useNavigate } from 'react-router-dom' + +import { StyledTagLink, TagText } from './TagLink.styles' + +const TagLink = ({ icon, to, children }) => { + const navigate = useNavigate() + + return ( + navigate(to)}> + {icon} + {children} + + ) +} + +export default TagLink diff --git a/client/src/components/Profile/components/ResumeInfo/TagLink/TagLink.styles.js b/client/src/components/Profile/components/ResumeInfo/TagLink/TagLink.styles.js new file mode 100644 index 000000000..240e85b55 --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/TagLink/TagLink.styles.js @@ -0,0 +1,23 @@ +import styled from 'styled-components' + +export const TagText = styled.p` + font-weight: 300; + font-size: 14px; +` + +export const StyledTagLink = styled.button` + cursor: pointer; + display: flex; + gap: 6px; + align-items: center; + padding: 5px 8px; + background-color: #2f3239; + border-radius: 5px; + transition: opacity 0.3s; + outline: none; + border: none; + color: white; + &:hover { + opacity: 0.8; + } +` diff --git a/client/src/components/RegistrationPipeline/components/RegistrationForms/InfoForm/UserInfoForm/UserInfoForm.jsx b/client/src/components/RegistrationPipeline/components/RegistrationForms/InfoForm/UserInfoForm/UserInfoForm.jsx index 3943f101c..79cd2ec99 100644 --- a/client/src/components/RegistrationPipeline/components/RegistrationForms/InfoForm/UserInfoForm/UserInfoForm.jsx +++ b/client/src/components/RegistrationPipeline/components/RegistrationForms/InfoForm/UserInfoForm/UserInfoForm.jsx @@ -1,8 +1,8 @@ import React, { useEffect, useState } from 'react' import countryList from 'react-select-country-list' -import { useValidateUsername } from '../../../../../../api/hooks/auth/useValidateUsername' import { useDebounce } from '../../../../../../api/hooks/temeights/useDebounce' +import { useValidateUsername } from '../../../../../../api/hooks/user/useValidateUsername' import CustomInput from '../../../../../../shared/components/Formik/CustomInput/CustomInput' import { GroupContainer, diff --git a/client/src/components/RegistrationPipeline/index.js b/client/src/components/RegistrationPipeline/index.js index f11d29a86..301ed915f 100644 --- a/client/src/components/RegistrationPipeline/index.js +++ b/client/src/components/RegistrationPipeline/index.js @@ -3,8 +3,8 @@ import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' -import { useEditUserDetails } from '../../api/hooks/auth/useEditUserDetails' -import { useUpdateAvatar } from '../../api/hooks/auth/useUpdateAvatar' +import { useUpdateAvatar } from '../../api/hooks/shared/useUpdateAvatar' +import { useEditUserDetails } from '../../api/hooks/user/useEditUserDetails' import { defaultUserAvatars } from '../../constants/finishRegistrationData' import { finishRegistrationValidation } from '../../schemas' import { setIsFinishRegistrationStarted, setStep } from '../../store/reducers/RegistrationAuth' diff --git a/client/src/components/Team/TeamForm/TeamForm.js b/client/src/components/Team/TeamForm/TeamForm.js index ba5301c49..53d65400f 100644 --- a/client/src/components/Team/TeamForm/TeamForm.js +++ b/client/src/components/Team/TeamForm/TeamForm.js @@ -5,7 +5,7 @@ import { Formik } from 'formik' // * API import { useCheckAuth } from '../../../api/hooks/auth/useCheckAuth' -import { useUpdateAvatar } from '../../../api/hooks/auth/useUpdateAvatar' +import { useUpdateAvatar } from '../../../api/hooks/shared/useUpdateAvatar' import { useDelete } from '../../../api/hooks/team/useDelete' import { useGetTeamData } from '../../../api/hooks/team/useGetTeamData' import { useInviteUser } from '../../../api/hooks/team/useInviteUser' diff --git a/client/src/components/Teammates/components/UserProfile/UserProfile.js b/client/src/components/Teammates/components/UserProfile/UserProfile.js index 20fef22eb..3adb935dd 100644 --- a/client/src/components/Teammates/components/UserProfile/UserProfile.js +++ b/client/src/components/Teammates/components/UserProfile/UserProfile.js @@ -1,5 +1,6 @@ // * Modules import React, { forwardRef, memo } from 'react' +import { useNavigate } from 'react-router-dom' import LongArrowRight from '../../../../assets/Arrows/LongArrowRight' import AddUserIcon from '../../../../assets/Shared/AddUserIcon' @@ -25,6 +26,8 @@ import { } from './UserProfile.styles' const UserProfile = ({ user, handleClose }, ref) => { + const navigate = useNavigate() + return ( @@ -85,7 +88,7 @@ const UserProfile = ({ user, handleClose }, ref) => { - diff --git a/client/src/components/Forms/TournamentsForm/TournamentsForm.js b/client/src/components/Tournaments/Tournaments.js similarity index 85% rename from client/src/components/Forms/TournamentsForm/TournamentsForm.js rename to client/src/components/Tournaments/Tournaments.js index a6c0d67bc..1944444fc 100644 --- a/client/src/components/Forms/TournamentsForm/TournamentsForm.js +++ b/client/src/components/Tournaments/Tournaments.js @@ -2,7 +2,7 @@ import { useNavigate } from 'react-router-dom' // * Assets -import JS from '../../../assets/LanguageLogo/JS' +import JS from '../../assets/LanguageLogo/JS' // * Styles import { @@ -13,8 +13,9 @@ import { Language, SmallCard, SmallCardContainer, + Span, Text, -} from './TournamentsForm.styles' +} from './Tournaments.styles' function TournamentsForm() { const navigate = useNavigate() @@ -47,7 +48,9 @@ function TournamentsForm() { - Tournaments coming in the next updates🎉 + + Tournaments coming in the next updates🎉 + ) diff --git a/client/src/components/Forms/TournamentsForm/TournamentsForm.styles.js b/client/src/components/Tournaments/Tournaments.styles.js similarity index 80% rename from client/src/components/Forms/TournamentsForm/TournamentsForm.styles.js rename to client/src/components/Tournaments/Tournaments.styles.js index 47559577e..c7ce152f3 100644 --- a/client/src/components/Forms/TournamentsForm/TournamentsForm.styles.js +++ b/client/src/components/Tournaments/Tournaments.styles.js @@ -1,6 +1,6 @@ import styled from 'styled-components' -import { BLACK, GREEN, WHITE } from '../../../constants/colors' +import { BLACK, GREEN, WHITE } from '../../constants/colors' export const Container = styled.div` width: 100%; @@ -20,6 +20,15 @@ export const Content = styled.div` flex-direction: column; align-items: center; gap: 55px; + padding-left: 88px; + + @media screen and (max-width: 1024px) { + text-align: center; + } + + @media screen and (max-width: 768px) { + padding: 0 20px; + } ` export const SmallCard = styled.div` @@ -30,6 +39,10 @@ export const SmallCard = styled.div` border-radius: 15px; padding: 20px 20px; display: flex; + + @media screen and (max-width: 1024px) { + display: none; + } ` export const SmallCardContainer = styled.div` @@ -59,6 +72,13 @@ export const Text = styled.h3` color: ${(props) => props.color || WHITE.main}; ` +export const Span = styled.span` + font-weight: 700; + font-size: 24px; + margin: 0; + color: #5bd424; +` + export const InnerText = styled.span` font-weight: ${(props) => props.fontWeight || '700'}; font-size: ${(props) => props.fontSize || '18px'}; @@ -75,6 +95,13 @@ export const BigCard = styled.div` display: flex; justify-content: center; align-items: center; + + @media screen and (max-width: 1024px) { + background: transparent; + box-shadow: none; + max-width: 774px; + width: 100%; + } ` export const ButtonGeneral = styled.button` diff --git a/client/src/constants/routes.js b/client/src/constants/routes.js index 4e7408c06..48f302280 100644 --- a/client/src/constants/routes.js +++ b/client/src/constants/routes.js @@ -8,8 +8,7 @@ const ROUTES = Object.freeze({ passwordRecover: '/auth/password-recover', passwordRecoverConfirm: '/auth/password-recover-confirm', passwordRecoverSuccess: '/auth/password-recover/:id/:token', - profile: '/profile', - profileEdit: '/profile-edit', + profile: '/profile/:id', tournaments: '/tournaments', specificTeam: '/team/:id', noTeam: '/team', diff --git a/client/src/routes/routes.js b/client/src/routes/routes.js index 8d5156728..613480358 100644 --- a/client/src/routes/routes.js +++ b/client/src/routes/routes.js @@ -2,9 +2,6 @@ import React from 'react' import { Route, Routes } from 'react-router-dom' -// * Components -import ProfileDetails from '../components/Profile/components/ProfileDetails/ProfileDetails' -import ProfileForm from '../components/Profile/components/ProfileForm' // * Constants import ROUTES from '../constants/routes' // * Layouts @@ -37,22 +34,7 @@ export const useRoutes = () => { {/* // * for authenticated user */} }> } /> - - - - } - /> - - - - } - /> + } /> } /> } /> } /> diff --git a/client/src/screens/ProfileScreen/ProfileScreen.js b/client/src/screens/ProfileScreen/ProfileScreen.js index 82a74f988..ad13ddef6 100644 --- a/client/src/screens/ProfileScreen/ProfileScreen.js +++ b/client/src/screens/ProfileScreen/ProfileScreen.js @@ -1,13 +1,13 @@ import React from 'react' import CssBaseline from '@mui/material/CssBaseline' -import { Cards } from '../../components/Profile/Profile.styles' +import Profile from '../../components/Profile/Profile' -function ProfileScreen({ children }) { +function ProfileScreen() { return ( <> - {children} + ) } diff --git a/client/src/screens/TournamentsScreen/Tournaments.js b/client/src/screens/TournamentsScreen/Tournaments.js index b1456a8d0..a6439f347 100644 --- a/client/src/screens/TournamentsScreen/Tournaments.js +++ b/client/src/screens/TournamentsScreen/Tournaments.js @@ -1,15 +1,15 @@ import React from 'react' import CssBaseline from '@mui/material/CssBaseline' -import TournamentsForm from '../../components/Forms/TournamentsForm/TournamentsForm' +import Tournaments from '../../components/Tournaments/Tournaments' -function Tournaments() { +function TournamentsScreen() { return ( <> - + ) } -export default Tournaments +export default TournamentsScreen diff --git a/client/src/shared/components/FlexWrapper/FlexWrapper.js b/client/src/shared/components/FlexWrapper/FlexWrapper.js index b68db471a..5ea3128c5 100644 --- a/client/src/shared/components/FlexWrapper/FlexWrapper.js +++ b/client/src/shared/components/FlexWrapper/FlexWrapper.js @@ -11,6 +11,7 @@ const StyledFlexWrapper = styled.div` max-height: ${(props) => props.maxHeight || 'none'}; position: ${(props) => props.position || 'static'}; width: ${(props) => props.width || 'auto'}; + height: ${(props) => props.height || 'auto'}; ` const FlexWrapper = (props) => { diff --git a/client/src/shared/styles/Global.styles.js b/client/src/shared/styles/Global.styles.js index 39aa00b41..a15d88758 100644 --- a/client/src/shared/styles/Global.styles.js +++ b/client/src/shared/styles/Global.styles.js @@ -10,6 +10,10 @@ export const GlobalStyle = createGlobalStyle` font-family: 'Rubik', sans-serif !important; } + html { + background: #26292B !important; + } + body { background: #26292B !important; overflow-y: scroll; From 176aa82383ccb27a0695da81aea26bd7a4586db9 Mon Sep 17 00:00:00 2001 From: Nikita Mashchenko Date: Wed, 28 Jun 2023 00:42:37 -0500 Subject: [PATCH 04/11] Made all buttons clickable & Modal animations --- client/src/components/Profile/Profile.jsx | 45 +++-- .../src/components/Profile/Profile.styles.js | 16 +- .../components/ProfileInfo/ProfileInfo.jsx | 123 ++++++++++-- .../ProfileInfo/ProfileInfo.styles.js | 6 +- .../components/ResumeInfo/ResumeInfo.jsx | 14 +- .../src/components/Team/Members/Members.jsx | 7 +- .../src/components/Team/Modal/TeamModal.jsx | 24 +-- .../Team/Modal/TeamModal.styles.jsx | 17 ++ .../Team/TeamForm/TeamForm.styles.js | 8 +- client/src/components/Teammates/Teammates.js | 16 +- .../components/Teammates/Teammates.styles.js | 12 +- .../Teammates/components/Cards/Cards.js | 11 +- .../components/Cards/Cards.styles.js | 1 - .../Teammates/components/UserCard/UserCard.js | 6 +- .../components/UserProfile/UserProfile.js | 178 ++++++++++++------ .../UserProfile/UserProfile.styles.js | 8 +- .../UserProfile/UserProfilePhone.js | 98 +++++++--- .../src/components/Tournaments/Tournaments.js | 2 +- .../components/Toasters/Error.toaster.js | 3 + .../components/Toasters/Info.toaster.js | 13 ++ client/src/utils/truncateString.js | 11 ++ 21 files changed, 456 insertions(+), 163 deletions(-) create mode 100644 client/src/shared/components/Toasters/Info.toaster.js create mode 100644 client/src/utils/truncateString.js diff --git a/client/src/components/Profile/Profile.jsx b/client/src/components/Profile/Profile.jsx index 98a4bab52..6f8280a93 100644 --- a/client/src/components/Profile/Profile.jsx +++ b/client/src/components/Profile/Profile.jsx @@ -22,15 +22,16 @@ import Page404Form from '../Forms/Page404Form/Page404Form' import ProfileInfo from './components/ProfileInfo/ProfileInfo' import ResumeInfo from './components/ResumeInfo/ResumeInfo' -import { ProfileContainer, ProfileWrapper } from './Profile.styles' +import { LogoWrapper, ProfileContainer, ProfileWrapper } from './Profile.styles' const Profile = () => { const navigate = useNavigate() const { id } = useParams() const { mutate: editUserDetails, isLoading } = useEditUserDetails() const { data, isLoading: isUserLoading, error } = useGetUserById(id) + const { data: currentUser, isFetching } = useCheckAuth() - const user = data?.data + const showingUser = data?.data const teamSearchHandler = () => { navigate('/teams') @@ -50,7 +51,7 @@ const Profile = () => { frameworks, } = values const modifiedUserData = { - email: user.email, + email: showingUser.email, fullName, description, concentration, @@ -68,31 +69,29 @@ const Profile = () => { editUserDetails(modifiedUserData) } - if (isLoading || isUserLoading) { + if (isLoading || isUserLoading || isFetching) { return } - if ((!isUserLoading && !user) || error) { - return + if ((!isUserLoading && !showingUser) || error) { + return } - /* Check if current userId is the same as passed in params */ - - /* If not, check if current user has team */ + /* If not, check if current showingUser has team and if team members have current id in array */ return ( { return (
    - {/* + - */} + - - + +
    diff --git a/client/src/components/Profile/Profile.styles.js b/client/src/components/Profile/Profile.styles.js index e890e2a11..55e374a40 100644 --- a/client/src/components/Profile/Profile.styles.js +++ b/client/src/components/Profile/Profile.styles.js @@ -8,6 +8,7 @@ export const ProfileWrapper = styled.div` min-height: 100dvh; padding-left: 88px; background: #26292b; + position: relative; @media (max-width: 768px) { padding-left: 0; @@ -19,11 +20,15 @@ export const ProfileContainer = styled.div` max-width: 800px; gap: 30px; margin: 0 auto; - padding: 0 15px; + padding: 0 25px; @media (max-width: 1024px) { flex-direction: column; align-items: center; + margin: 114px 0 24px 0; + } + + @media (max-width: 768px) { margin: 96px 0 24px 0; } ` @@ -44,3 +49,12 @@ export const ProfileSection = styled.div` width: 100%; } ` + +export const LogoWrapper = styled.div` + position: absolute; + top: 48px; + + @media (max-width: 768px) { + top: 32px; + } +` diff --git a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx index 01de21da9..9a379327c 100644 --- a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx +++ b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx @@ -1,15 +1,24 @@ +import { ThreeDots } from 'react-loader-spinner' +import { useNavigate } from 'react-router-dom' + +import { useInviteUser } from '../../../../api/hooks/team/useInviteUser' +import LongArrowLeft from '../../../../assets/Arrows/LongArrowLeft' import BehanceIcon from '../../../../assets/Links/BehanceIcon' import GitHubIcon from '../../../../assets/Links/GitHubIcon' import LinkedInIcon from '../../../../assets/Links/LinkedInIcon' import TelegramIcon from '../../../../assets/Links/TelegramIcon' +import AddUserIcon from '../../../../assets/Shared/AddUserIcon' import CakeIcon from '../../../../assets/UserProfile/Cake' import EmailIcon from '../../../../assets/UserProfile/EmailIcon' import LocationIcon from '../../../../assets/UserProfile/LocationIcon' import MessageIcon from '../../../../assets/UserProfile/MessageIcon' import StarIcon from '../../../../assets/UserProfile/StarIcon' import UserIcon from '../../../../assets/UserProfile/UserIcon' +import { useGetScreenWidth } from '../../../../hooks/useGetScreenWidth' import FlexWrapper from '../../../../shared/components/FlexWrapper/FlexWrapper' +import { infoToaster } from '../../../../shared/components/Toasters/Info.toaster' import { calculateAge } from '../../../../utils/calculateAge' +import { truncateString } from '../../../../utils/truncateString' import { ProfileSection } from '../../Profile.styles' import { @@ -23,45 +32,123 @@ import { UserInfo, } from './ProfileInfo.styles' -const ProfileInfo = ({ user }) => { +const ProfileInfo = ({ showingUser, id, currentUser }) => { + const width = useGetScreenWidth() + const navigate = useNavigate() + const { mutate: inviteUser, isLoading: isInviting } = useInviteUser() + const infoListArr = [ - { icon: , infoEl: user?.concentration }, - { icon: , infoEl: `${user?.experience} years of experience` }, - { icon: , infoEl: user?.country }, - { icon: , infoEl: `${calculateAge(user?.dateOfBirth)} years old` }, + { icon: , infoEl: truncateString(showingUser?.concentration, width) }, + { + icon: , + infoEl: truncateString(`${showingUser?.experience} years of experience`, width), + }, + { icon: , infoEl: truncateString(showingUser?.country, width) }, + { + icon: , + infoEl: truncateString(`${calculateAge(showingUser?.dateOfBirth)} years old`, width), + }, { icon: , infoEl: ( - - {user?.email} + + {truncateString(showingUser?.email, width)} ), }, ] const socialArr = [ - user?.links?.github && { icon: , link: user?.links?.github }, - user?.links?.behance && { icon: , link: user?.links?.behance }, - user?.links?.telegram && { icon: , link: user?.links?.telegram }, - user?.links?.linkedIn && { icon: , link: user?.links?.linkedIn }, + showingUser?.links?.github && { icon: , link: showingUser?.links?.github }, + showingUser?.links?.behance && { icon: , link: showingUser?.links?.behance }, + showingUser?.links?.telegram && { icon: , link: showingUser?.links?.telegram }, + showingUser?.links?.linkedIn && { icon: , link: showingUser?.links?.linkedIn }, ].filter(Boolean) + /* Check if current showingUserId is the same as passed in params */ + const checkUserStatus = () => { + if (currentUser?._id === id) { + return 'same' + } else if (currentUser?.team && currentUser?.team?.members.some((member) => member === id)) { + return 'teammember' + } else { + return 'other' + } + } + + const handleInvite = () => { + const details = { + email: showingUser.email, + teamid: currentUser?.team?._id, + from_user_id: currentUser?._id, + } + + inviteUser(details) + } + return ( - + - {user?.fullName} + {showingUser?.fullName} - @{user?.username} + @{showingUser?.username} - - Message - - + {checkUserStatus() === 'same' && ( + + This is your profile + + )} + {checkUserStatus() === 'teammember' && ( + + navigate(-1)}> + + Back + + infoToaster('Coming in the next update!')} + background="#46A11B" + > + Message + + + + )} + {checkUserStatus() === 'other' && ( + + {currentUser?.team && ( + + {isInviting ? ( + + ) : ( + <> + Invite + + + )} + + )} + + infoToaster('Coming in the next update!')}> + Message + + + + )} {infoListArr.map((item, index) => ( diff --git a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js index ad9b7ecbe..a38bffbbe 100644 --- a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js +++ b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js @@ -34,9 +34,9 @@ export const MessageBtn = styled.button` gap: 6px; width: 100%; max-width: 222px; - background: transparent; + background: ${(props) => props.background || 'transparent'}; padding: 10px 0; - border: 2px solid #46a11b; + border: ${(props) => props.border || '2px solid #46a11b'}; border-radius: 10px; transition: background-color 0.3s; color: #fff; @@ -74,6 +74,8 @@ export const InfoListItem = styled.li` display: flex; align-items: center; gap: 8px; + width: 100%; + flex-wrap: wrap; ` export const SocialList = styled.ul` diff --git a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx index dba839b96..dc3fdba61 100644 --- a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx +++ b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx @@ -17,7 +17,7 @@ import { WrappableList, } from './ResumeInfo.styles' -const ResumeInfo = ({ user }) => { +const ResumeInfo = ({ showingUser }) => { const projectsArr = [ { text: 'Team8s', link: '#' }, { text: 'BankingApp', link: '#' }, @@ -45,16 +45,16 @@ const ResumeInfo = ({ user }) => { About me - {user?.description ? user?.description : 'This user is humble'} + {showingUser?.description ? showingUser?.description : 'This showingUser is humble'} Team - {user?.team ? ( - } to={`/team/${user?.team._id}`}> - {user?.team.name} + {showingUser?.team ? ( + } to={`/team/${showingUser?.team._id}`}> + {showingUser?.team.name} ) : ( @@ -81,7 +81,7 @@ const ResumeInfo = ({ user }) => { Frameworks - {user?.frameworks?.map((framework, index) => ( + {showingUser?.frameworks?.map((framework, index) => ( { Languages - {user?.programmingLanguages?.map((language, index) => ( + {showingUser?.programmingLanguages?.map((language, index) => ( {languageOptions[language]} ))} diff --git a/client/src/components/Team/Members/Members.jsx b/client/src/components/Team/Members/Members.jsx index 428c39938..566291784 100644 --- a/client/src/components/Team/Members/Members.jsx +++ b/client/src/components/Team/Members/Members.jsx @@ -1,4 +1,5 @@ import { useState } from 'react' +import { useNavigate } from 'react-router-dom' import SCrownRight from '../../../assets/Shared/Crowns/SCrownRight' import Chat from '../../../assets/Team/Chat' @@ -7,6 +8,7 @@ import UserPlus from '../../../assets/Team/UserPlus' import { B2fs, B2fw, B2lh, B3fs, B3fw, B3lh } from '../../../constants/fonts' import { useGetScreenWidth } from '../../../hooks/useGetScreenWidth' import { LOCAL_PATH } from '../../../http' +import { infoToaster } from '../../../shared/components/Toasters/Info.toaster' import { getCountryFlag } from '../../../utils/getCountryFlag' import { TeamCardTopIcon } from '../Modal/TeamPreviewModal/TeamPreviewModal.styles' import { Text, UserPlusContainer } from '../TeamForm/TeamForm.styles' @@ -40,6 +42,7 @@ const Members = ({ }) => { const [hoveredCardId, setHoveredCardId] = useState(null) const screenWidth = useGetScreenWidth() + const navigate = useNavigate() const handleMouseEnter = (cardId) => { setHoveredCardId(cardId) @@ -71,11 +74,11 @@ const Members = ({ )} {hoveredCardId === member._id && !isEditing ? ( - console.log('TODO: add transition to chat')}> + infoToaster('Coming in the next update!')}> Chat - console.log('TODO: add transition to profile')}> + navigate(`/profile/${member?._id}`)}> diff --git a/client/src/components/Team/Modal/TeamModal.jsx b/client/src/components/Team/Modal/TeamModal.jsx index cd907de32..3ccf88ce1 100644 --- a/client/src/components/Team/Modal/TeamModal.jsx +++ b/client/src/components/Team/Modal/TeamModal.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react' -import { Box, Modal } from '@mui/material' +import { Box, Modal, Slide } from '@mui/material' import Close from '../../../assets/Shared/Close' import UserPlus from '../../../assets/Team/UserPlus' @@ -24,7 +24,7 @@ import InteractiveModal from './ModalTypes/InteractiveModal' import TeamPreviewModal from './TeamPreviewModal/TeamPreviewModal' import TeamPreviewModalPhone from './TeamPreviewModalPhone/TeamPreviewModalPhone' import { MobileProfile } from './TeamPreviewModalPhone/TeamPreviewModalPhone.styles' -import { Button } from './TeamModal.styles' +import { Button, TeamDesktopModal } from './TeamModal.styles' const TeamModal = ({ modalActive, @@ -267,20 +267,22 @@ const TeamModal = ({ return ( <> {width > 600 && ( - - - - - - {handleModal()} - - + + + + + + {handleModal()} + + + )} {width <= 600 && ( diff --git a/client/src/components/Team/Modal/TeamModal.styles.jsx b/client/src/components/Team/Modal/TeamModal.styles.jsx index 3eb3ec119..d44959daa 100644 --- a/client/src/components/Team/Modal/TeamModal.styles.jsx +++ b/client/src/components/Team/Modal/TeamModal.styles.jsx @@ -1,3 +1,4 @@ +import { Modal } from '@mui/material' import styled from 'styled-components' import { B2fs, B2fw, B2lh } from '../../../constants/fonts' @@ -94,3 +95,19 @@ export const NoMembersCard = styled.div` justify-content: center; align-items: center; ` + +export const TeamDesktopModal = styled(Modal)` + backdrop-filter: blur(5px); + display: flex; + align-items: center; + justify-content: center; + padding-left: 88px; + + @media screen and (max-width: 768px) { + padding: 0; + } + + @media screen and (max-width: 520px) { + display: none; + } +` diff --git a/client/src/components/Team/TeamForm/TeamForm.styles.js b/client/src/components/Team/TeamForm/TeamForm.styles.js index a6bc2c220..4cf834e8e 100644 --- a/client/src/components/Team/TeamForm/TeamForm.styles.js +++ b/client/src/components/Team/TeamForm/TeamForm.styles.js @@ -281,12 +281,10 @@ export const FormikContainer = styled.div` export const style = { position: 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', width: '370px', height: '350px', bgcolor: '#1A1C22', + margin: 'auto', borderRadius: '15px', boxShadow: 14, padding: '32px 32px', @@ -295,9 +293,7 @@ export const style = { export const teamPreviewStyle = { position: 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', + margin: 'auto', width: '570px', bgcolor: '#1A1C22', borderRadius: '15px', diff --git a/client/src/components/Teammates/Teammates.js b/client/src/components/Teammates/Teammates.js index 707ad9330..d85de9463 100644 --- a/client/src/components/Teammates/Teammates.js +++ b/client/src/components/Teammates/Teammates.js @@ -68,12 +68,22 @@ function Teammates() { onClose={handleClose} aria-labelledby="modal-modal-title" aria-describedby="modal-modal-description" - sx={{ backdropFilter: 'blur(15px)' }} + sx={{ zIndex: 100 }} > - + {/* ! USED ONLY FOR 730px or less */} - + {/* If nothing was found, show user a NotFound container */} {isNotFound ? ( diff --git a/client/src/components/Teammates/Teammates.styles.js b/client/src/components/Teammates/Teammates.styles.js index 7d831ae33..fa71e1de7 100644 --- a/client/src/components/Teammates/Teammates.styles.js +++ b/client/src/components/Teammates/Teammates.styles.js @@ -66,7 +66,17 @@ export const InfoContainer = styled.div` ` export const UserCardModal = styled(Modal)` - @media screen and (min-width: 0px) and (max-width: 520px) { + backdrop-filter: blur(5px); + display: flex; + align-items: center; + justify-content: center; + padding-left: 88px; + + @media screen and (max-width: 768px) { + padding: 0; + } + + @media screen and (max-width: 520px) { display: none; } ` diff --git a/client/src/components/Teammates/components/Cards/Cards.js b/client/src/components/Teammates/components/Cards/Cards.js index 1498488c8..7563cd89c 100644 --- a/client/src/components/Teammates/components/Cards/Cards.js +++ b/client/src/components/Teammates/components/Cards/Cards.js @@ -1,5 +1,5 @@ // * Modules -import React, { useCallback, useEffect, useRef } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' // * Constants // * API import lookup from 'country-code-lookup' @@ -15,6 +15,7 @@ import { CardContainer } from './Cards.styles' const Cards = ({ handleOpen, isLoadingUseData, setIsNotFound }) => { const intObserver = useRef() + const [loadedPictures, setLoadedPictures] = useState(0) const { fetchNextPage, @@ -83,6 +84,7 @@ const Cards = ({ handleOpen, isLoadingUseData, setIsNotFound }) => { ref={lastUserRef} key={user._id} person={user} + setLoadedPictures={setLoadedPictures} /> ) @@ -95,7 +97,12 @@ const Cards = ({ handleOpen, isLoadingUseData, setIsNotFound }) => { }} key={index} > - + ) }) diff --git a/client/src/components/Teammates/components/Cards/Cards.styles.js b/client/src/components/Teammates/components/Cards/Cards.styles.js index dc794ba3a..ceb054dab 100644 --- a/client/src/components/Teammates/components/Cards/Cards.styles.js +++ b/client/src/components/Teammates/components/Cards/Cards.styles.js @@ -5,5 +5,4 @@ export const CardContainer = styled.div` display: flex; /* new */ align-items: center; /* new */ justify-content: center; /* new */ - z-index: 200; /* z-index should be higher than opacity */ ` diff --git a/client/src/components/Teammates/components/UserCard/UserCard.js b/client/src/components/Teammates/components/UserCard/UserCard.js index 49dc0fe74..1dabbc5c8 100644 --- a/client/src/components/Teammates/components/UserCard/UserCard.js +++ b/client/src/components/Teammates/components/UserCard/UserCard.js @@ -91,7 +91,11 @@ const UserCard = React.forwardRef((props, ref) => { 2} ufLength={ufLength > 4}>
    - + props.setLoadedPictures((prev) => prev + 1)} + />
    {programmingLanguages}
    diff --git a/client/src/components/Teammates/components/UserProfile/UserProfile.js b/client/src/components/Teammates/components/UserProfile/UserProfile.js index 3adb935dd..7aede6dbe 100644 --- a/client/src/components/Teammates/components/UserProfile/UserProfile.js +++ b/client/src/components/Teammates/components/UserProfile/UserProfile.js @@ -1,13 +1,17 @@ // * Modules import React, { forwardRef, memo } from 'react' +import { ThreeDots } from 'react-loader-spinner' import { useNavigate } from 'react-router-dom' +import { Slide } from '@mui/material' +import { useInviteUser } from '../../../../api/hooks/team/useInviteUser' import LongArrowRight from '../../../../assets/Arrows/LongArrowRight' import AddUserIcon from '../../../../assets/Shared/AddUserIcon' import Close from '../../../../assets/Shared/Close' import Message from '../../../../assets/Shared/Message' import { frameworkColors, frameworkTextColors } from '../../../../constants/frameworkColors' import { languageOptions } from '../../../../constants/programmingLanguages' +import { infoToaster } from '../../../../shared/components/Toasters/Info.toaster' // * Assets import { calculateAge } from '../../../../utils/calculateAge' import { getCountryFlag } from '../../../../utils/getCountryFlag' @@ -25,77 +29,131 @@ import { UserImg, } from './UserProfile.styles' -const UserProfile = ({ user, handleClose }, ref) => { +const UserProfile = ({ showingUser, currentUser, handleClose, open }, ref) => { const navigate = useNavigate() + const { mutate: inviteUser, isLoading: isInviting } = useInviteUser() + + const handleInvite = () => { + const details = { + email: showingUser.email, + teamid: currentUser?.team?._id, + from_user_id: currentUser?._id, + } + + inviteUser(details) + } + + const showInviteButton = () => { + /** Check if current showingUser has a team */ + if (currentUser?.team) { + /** Check if current showingUser has showing showingUser as team member */ + if (!currentUser?.team?.members?.some((member) => member === showingUser?._id)) { + return true + } + } + + return false + } + return ( - - - - - - - -
    - -
    - - - - {user?.fullName?.split(' ')[0]}, {calculateAge(user?.dateOfBirth)} + + + + + + + + +
    + +
    + + + + {showingUser?.fullName?.split(' ')[0]}, {calculateAge(showingUser?.dateOfBirth)} + + {getCountryFlag(showingUser.country) && ( + + )} + + + {showingUser?.concentration} + + + {showingUser?.experience} years of experience - {getCountryFlag(user.country) && } - - {user?.concentration} - - - {user?.experience} years of experience +
    + {showingUser?.description && ( + + {showingUser.description} + )} + + {showingUser?.frameworks?.map((framework) => ( + +

    {framework}

    +
    + ))}
    -
    - {user?.description && ( - - {user.description} - - )} - - {user?.frameworks?.map((framework) => ( - + {showingUser?.programmingLanguages?.map((language) => ( + {languageOptions[language]} + ))} + + + + {showInviteButton() && ( + + )} + + + - -
    -
    -
    -
    +
    +
    + ) } diff --git a/client/src/components/Teammates/components/UserProfile/UserProfile.styles.js b/client/src/components/Teammates/components/UserProfile/UserProfile.styles.js index 8d8212f2c..26608fb15 100644 --- a/client/src/components/Teammates/components/UserProfile/UserProfile.styles.js +++ b/client/src/components/Teammates/components/UserProfile/UserProfile.styles.js @@ -4,11 +4,13 @@ import styled from 'styled-components' export const Container = styled(Box)` position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + /* top: 50%; */ + /* left: 0%; */ + margin: auto; + /* transform: translate(-50%, -50%); */ width: 470px; height: 530px; + outline: none; ` export const ProfileContainer = styled.div` diff --git a/client/src/components/Teammates/components/UserProfile/UserProfilePhone.js b/client/src/components/Teammates/components/UserProfile/UserProfilePhone.js index 6f3471bc7..e6a703519 100644 --- a/client/src/components/Teammates/components/UserProfile/UserProfilePhone.js +++ b/client/src/components/Teammates/components/UserProfile/UserProfilePhone.js @@ -1,5 +1,8 @@ import React, { useState } from 'react' +import { ThreeDots } from 'react-loader-spinner' +import { useNavigate } from 'react-router-dom' +import { useInviteUser } from '../../../../api/hooks/team/useInviteUser' import LongArrowLeft from '../../../../assets/Arrows/LongArrowLeft' import LongArrowRight from '../../../../assets/Arrows/LongArrowRight' // * Assets @@ -8,6 +11,7 @@ import Close from '../../../../assets/Shared/Close' import Message from '../../../../assets/Shared/Message' import { frameworkColors, frameworkTextColors } from '../../../../constants/frameworkColors' import { languageOptions } from '../../../../constants/programmingLanguages' +import { infoToaster } from '../../../../shared/components/Toasters/Info.toaster' import { calculateAge } from '../../../../utils/calculateAge' import { getCountryFlag } from '../../../../utils/getCountryFlag' @@ -24,7 +28,32 @@ import { UserImg, } from './UserProfile.styles' -const UserProfilePhone = ({ user, mobileProfile, handleClose }) => { +const UserProfilePhone = ({ currentUser, showingUser, mobileProfile, handleClose }) => { + const navigate = useNavigate() + const { mutate: inviteUser, isLoading: isInviting } = useInviteUser() + + const handleInvite = () => { + const details = { + email: showingUser.email, + teamid: currentUser?.team?._id, + from_user_id: currentUser?._id, + } + + inviteUser(details) + } + + const showInviteButton = () => { + /** Check if current user has a team */ + if (currentUser?.team) { + /** Check if current user has showing user as team member */ + if (!currentUser?.team?.members?.some((member) => member === showingUser?._id)) { + return true + } + } + + return false + } + return ( @@ -33,7 +62,11 @@ const UserProfilePhone = ({ user, mobileProfile, handleClose }) => { Back - @@ -41,42 +74,65 @@ const UserProfilePhone = ({ user, mobileProfile, handleClose }) => {
    - +
    - {user?.fullName?.split(' ')[0]}, {calculateAge(user.dateOfBirth)} + {showingUser?.fullName?.split(' ')[0]}, {calculateAge(showingUser.dateOfBirth)} - {getCountryFlag(user.country) && } + {getCountryFlag(showingUser?.country) && ( + + )} - {user.concentration} + {showingUser?.concentration} - {user.experience} years of experience + {showingUser?.experience} years of experience
    - - - - - + )} + + - {user?.description && ( + {showingUser?.description && ( - {user.description} + {showingUser?.description} )} - {user?.frameworks?.map((framework) => ( + {showingUser?.frameworks?.map((framework) => ( { ))} - {user?.programmingLanguages?.map((language) => ( + {showingUser?.programmingLanguages?.map((language) => ( {languageOptions[language]} diff --git a/client/src/components/Tournaments/Tournaments.js b/client/src/components/Tournaments/Tournaments.js index 1944444fc..254888851 100644 --- a/client/src/components/Tournaments/Tournaments.js +++ b/client/src/components/Tournaments/Tournaments.js @@ -29,7 +29,7 @@ function TournamentsForm() { Teameights cup #1 - + All roles   are welcome! diff --git a/client/src/shared/components/Toasters/Error.toaster.js b/client/src/shared/components/Toasters/Error.toaster.js index e9253354f..ba8b543fc 100644 --- a/client/src/shared/components/Toasters/Error.toaster.js +++ b/client/src/shared/components/Toasters/Error.toaster.js @@ -6,6 +6,7 @@ export const errorToaster = (error) => { toast.error(error, { id: error, style: { background: '#2F3239', color: 'white' }, + duration: 2000, }) } // Display the toast with the error message @@ -13,6 +14,7 @@ export const errorToaster = (error) => { toast.error(error?.response?.data?.message, { id: error?.response?.data?.message, style: { background: '#2F3239', color: 'white' }, + duration: 2000, }) } else { let errors = error?.response?.data @@ -24,6 +26,7 @@ export const errorToaster = (error) => { toast.error(word, { id: word, style: { background: '#2F3239', color: 'white' }, + duration: 2000, }) }, i * 300) // Delay each notification by i * 100 milliseconds } diff --git a/client/src/shared/components/Toasters/Info.toaster.js b/client/src/shared/components/Toasters/Info.toaster.js new file mode 100644 index 000000000..aef3ce20b --- /dev/null +++ b/client/src/shared/components/Toasters/Info.toaster.js @@ -0,0 +1,13 @@ +import { toast } from 'react-hot-toast' + +export const infoToaster = (message) => { + if (typeof message === 'string') { + toast(message, { + id: message, + style: { background: '#2F3239', color: 'white' }, + duration: 1000, + icon: '⚡️', + position: 'bottom-right', + }) + } +} diff --git a/client/src/utils/truncateString.js b/client/src/utils/truncateString.js new file mode 100644 index 000000000..3fbbebe00 --- /dev/null +++ b/client/src/utils/truncateString.js @@ -0,0 +1,11 @@ +export const truncateString = (str, screenWidth) => { + if (str.length > 23 && screenWidth > 1024) { + let halfLength = Math.floor((23 - 3) / 2) // Subtracting 3 for the ellipsis + let firstHalf = str.substr(0, halfLength) + let secondHalf = str.substr(str.length - halfLength) + + return firstHalf + '...' + secondHalf + } + + return str +} From ba00359e1f9d1fc62bcc2d587ce5d458bee4065c Mon Sep 17 00:00:00 2001 From: Nikita Mashchenko Date: Wed, 28 Jun 2023 03:50:26 -0500 Subject: [PATCH 05/11] Added Education&Work --- client/src/assets/UserProfile/EditIcon.js | 14 +++ .../src/components/Profile/Profile.styles.js | 4 + .../components/ProfileInfo/ProfileInfo.jsx | 5 + .../ProfileInfo/ProfileInfo.styles.js | 15 +++ .../EducationWork/EducationWork.jsx | 95 +++++++++++++++++++ .../ProjectsSkills/ProjectsSkills.jsx | 86 +++++++++++++++++ .../components/ResumeInfo/ResumeInfo.jsx | 94 ++---------------- 7 files changed, 226 insertions(+), 87 deletions(-) create mode 100644 client/src/assets/UserProfile/EditIcon.js create mode 100644 client/src/components/Profile/components/ResumeInfo/EducationWork/EducationWork.jsx create mode 100644 client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx diff --git a/client/src/assets/UserProfile/EditIcon.js b/client/src/assets/UserProfile/EditIcon.js new file mode 100644 index 000000000..8d2f0ad36 --- /dev/null +++ b/client/src/assets/UserProfile/EditIcon.js @@ -0,0 +1,14 @@ +function EditIcon() { + return ( + + + + ) +} + +export default EditIcon diff --git a/client/src/components/Profile/Profile.styles.js b/client/src/components/Profile/Profile.styles.js index 55e374a40..c8a8b5be7 100644 --- a/client/src/components/Profile/Profile.styles.js +++ b/client/src/components/Profile/Profile.styles.js @@ -26,6 +26,8 @@ export const ProfileContainer = styled.div` flex-direction: column; align-items: center; margin: 114px 0 24px 0; + max-width: 600px; + width: 100%; } @media (max-width: 768px) { @@ -47,6 +49,8 @@ export const ProfileSection = styled.div` @media (max-width: 1024px) { max-width: 470px; width: 100%; + min-height: 600px; + height: 100%; } ` diff --git a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx index 9a379327c..46bfcc74d 100644 --- a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx +++ b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx @@ -9,6 +9,7 @@ import LinkedInIcon from '../../../../assets/Links/LinkedInIcon' import TelegramIcon from '../../../../assets/Links/TelegramIcon' import AddUserIcon from '../../../../assets/Shared/AddUserIcon' import CakeIcon from '../../../../assets/UserProfile/Cake' +import EditIcon from '../../../../assets/UserProfile/EditIcon' import EmailIcon from '../../../../assets/UserProfile/EmailIcon' import LocationIcon from '../../../../assets/UserProfile/LocationIcon' import MessageIcon from '../../../../assets/UserProfile/MessageIcon' @@ -24,6 +25,7 @@ import { ProfileSection } from '../../Profile.styles' import { AvatarImg, AvatarWrapper, + EditButton, InfoList, InfoListItem, MessageBtn, @@ -91,6 +93,9 @@ const ProfileInfo = ({ showingUser, id, currentUser }) => { + + + {showingUser?.fullName} diff --git a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js index a38bffbbe..d0f522fd0 100644 --- a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js +++ b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js @@ -91,3 +91,18 @@ export const SocialList = styled.ul` } } ` + +export const EditButton = styled.div` + width: 28px; + height: 28px; + position: absolute; + background: #46a11b; + right: 0px; + bottom: 0px; + display: flex; + /* padding: 4px; */ + justify-content: center; + align-items: center; + border-radius: 100px; + cursor: pointer; +` diff --git a/client/src/components/Profile/components/ResumeInfo/EducationWork/EducationWork.jsx b/client/src/components/Profile/components/ResumeInfo/EducationWork/EducationWork.jsx new file mode 100644 index 000000000..982d682cf --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/EducationWork/EducationWork.jsx @@ -0,0 +1,95 @@ +import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' +import { Text } from '../ResumeInfo.styles' + +const EducationWork = ({ showingUser }) => { + return ( + <> + + + Education + + {showingUser?.universityData?.length > 0 ? ( + showingUser?.universityData.map((university, index) => ( + + + {university.degree} - {university.major} + + + {university.university} + + + {university?.addmissionDate?.split('-')[0]} -{' '} + {university?.graduationDate?.split('-')[0]} + + + )) + ) : ( + + User has no education. + + )} + {/* + + Bachelor’s Degree - Economics and Managment + + + National University of Food Technologies + + + 2019-2023 + + + + + Bachelor’s Degree - Economics and Managment + + + National University of Food Technologies + + + 2019-2023 + + */} + + + + Work experience + + {showingUser?.jobData?.length > 0 ? ( + showingUser?.jobData.map((job, index) => ( + + + {job.title} - {job.company} + + + {job.startDate.split('-')[0]} - {job.endDate.split('-')[0]} + + + )) + ) : ( + + User has no working experience. + + )} + {/* + + Front-end Developer - Team8s StartUp + + + 2019-2023 + + + + + UX/UI designer - League Agency + + + 2019-2023 + + */} + + + ) +} + +export default EducationWork diff --git a/client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx b/client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx new file mode 100644 index 000000000..7940b11c7 --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx @@ -0,0 +1,86 @@ +import LinkIcon from '../../../../../assets/UserProfile/LinkIcon' +import TeamMembersIcon from '../../../../../assets/UserProfile/TeamMembersIcon' +import { frameworkColors, frameworkTextColors } from '../../../../../constants/frameworkColors' +import { languageOptions } from '../../../../../constants/programmingLanguages' +import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' +import { FrameWorkItem, LanguageItem, Text, WrappableList } from '../ResumeInfo.styles' +import TagLink from '../TagLink/TagLink' + +const ProjectsSkills = ({ showingUser }) => { + const projectsArr = [ + { text: 'Team8s', link: '#' }, + { text: 'BankingApp', link: '#' }, + { text: 'Snake', link: '#' }, + { text: 'Shopping App', link: '#' }, + { text: 'E-learning', link: '#' }, + ] + + return ( + + + + About me + + + {showingUser?.description ? showingUser?.description : 'This showingUser is humble'} + + + + + Team + + {showingUser?.team ? ( + } to={`/team/${showingUser?.team._id}`}> + {showingUser?.team.name} + + ) : ( + + No team yet. + + )} + + + + Projects + + + {projectsArr.map(({ link, text }, index) => ( +
  • + }> + {text} + +
  • + ))} +
    +
    + + + Frameworks + + + {showingUser?.frameworks?.map((framework, index) => ( + + {framework} + + ))} + + + + + Languages + + + {showingUser?.programmingLanguages?.map((language, index) => ( + {languageOptions[language]} + ))} + + +
    + ) +} + +export default ProjectsSkills diff --git a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx index dc3fdba61..d6bfaa02f 100644 --- a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx +++ b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx @@ -1,36 +1,18 @@ import { useState } from 'react' -import LinkIcon from '../../../../assets/UserProfile/LinkIcon' -import TeamMembersIcon from '../../../../assets/UserProfile/TeamMembersIcon' -import { frameworkColors, frameworkTextColors } from '../../../../constants/frameworkColors' -import { languageOptions } from '../../../../constants/programmingLanguages' import FlexWrapper from '../../../../shared/components/FlexWrapper/FlexWrapper' import { ProfileSection } from '../../Profile.styles' -import TagLink from './TagLink/TagLink' -import { - FrameWorkItem, - LanguageItem, - ResumePartBox, - ResumePartBtn, - Text, - WrappableList, -} from './ResumeInfo.styles' +import EducationWork from './EducationWork/EducationWork' +import ProjectsSkills from './ProjectsSkills/ProjectsSkills' +import { ResumePartBox, ResumePartBtn } from './ResumeInfo.styles' const ResumeInfo = ({ showingUser }) => { - const projectsArr = [ - { text: 'Team8s', link: '#' }, - { text: 'BankingApp', link: '#' }, - { text: 'Snake', link: '#' }, - { text: 'Shopping App', link: '#' }, - { text: 'E-learning', link: '#' }, - ] - const [active, setActive] = useState('projects') return ( - - + + setActive('projects')}> Projects & Skills @@ -39,70 +21,8 @@ const ResumeInfo = ({ showingUser }) => { Education & Work - - - - About me - - - {showingUser?.description ? showingUser?.description : 'This showingUser is humble'} - - - - - Team - - {showingUser?.team ? ( - } to={`/team/${showingUser?.team._id}`}> - {showingUser?.team.name} - - ) : ( - - No team yet. - - )} - - - - Projects - - - {projectsArr.map(({ link, text }, index) => ( -
  • - }> - {text} - -
  • - ))} -
    -
    - - - Frameworks - - - {showingUser?.frameworks?.map((framework, index) => ( - - {framework} - - ))} - - - - - Languages - - - {showingUser?.programmingLanguages?.map((language, index) => ( - {languageOptions[language]} - ))} - - -
    + {active === 'projects' && } + {active === 'education' && }
    ) From 1bb6e624a2b45542b0669f28a5e07fc0af0eadb4 Mon Sep 17 00:00:00 2001 From: Nikita Mashchenko Date: Thu, 29 Jun 2023 03:59:44 -0500 Subject: [PATCH 06/11] Updates & bug fixes --- .../src/api/hooks/shared/useUpdateAvatar.js | 2 + .../src/api/hooks/user/useEditUserDetails.js | 9 +- .../src/components/NavBar/Profile/Profile.jsx | 2 + client/src/components/Profile/Profile.jsx | 70 +++-- .../src/components/Profile/Profile.styles.js | 29 ++- .../components/ProfileInfo/ProfileInfo.jsx | 104 +++----- .../ProfileInfo/ProfileInfo.styles.js | 3 +- .../UserStatusButtons/UserStatusButtons.jsx | 105 ++++++++ .../EditingComponentAvatar.jsx | 24 ++ .../EditingComponentDefault.jsx | 23 ++ .../EditingComponentProfile.jsx | 62 +++++ .../components/ResumeInfo/ResumeInfo.jsx | 57 ++-- .../ResumeInfo/ResumeInfo.styles.js | 21 +- .../AvatarForm/AvatarForm.jsx | 4 +- .../AvatarSelection/AvatarSelection.jsx | 6 +- .../CustomDropZone/CustomDropZone.jsx | 36 --- .../components/RegistrationPipeline/index.js | 6 +- client/src/components/Search/Search.js | 5 +- .../components/Team/EditImage/EditImage.jsx | 188 -------------- .../Team/EditImage/EditImage.styles.js | 91 ------- .../Team/TeamForm/ActionButtonsType.js | 11 +- .../src/components/Team/TeamForm/TeamForm.js | 28 +- .../TeamProfileLargeCard.jsx | 21 +- .../TeamProfileMiniCard.jsx | 19 +- client/src/constants/countries.js | 245 ++++++++++++++++++ .../components/ChooseAvatar/ChooseAvatar.jsx | 27 +- .../ChooseAvatar/ChooseAvatar.styles.js | 24 +- .../CustomDropZone/CustomDropZone.jsx | 73 ++++++ .../CustomDropZone/CustomDropZone.styles.js | 1 + client/src/utils/checkUserStatus.js | 10 + client/src/utils/getServedProfilepic.js | 12 - client/src/utils/truncateString.js | 4 +- 32 files changed, 770 insertions(+), 552 deletions(-) create mode 100644 client/src/components/Profile/components/ProfileInfo/UserStatusButtons/UserStatusButtons.jsx create mode 100644 client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentAvatar.jsx create mode 100644 client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDefault.jsx create mode 100644 client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProfile.jsx delete mode 100644 client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.jsx delete mode 100644 client/src/components/Team/EditImage/EditImage.jsx delete mode 100644 client/src/components/Team/EditImage/EditImage.styles.js create mode 100644 client/src/constants/countries.js rename client/src/{components/RegistrationPipeline/components/RegistrationForms/AvatarForm => shared}/components/ChooseAvatar/ChooseAvatar.jsx (65%) rename client/src/{components/RegistrationPipeline/components/RegistrationForms/AvatarForm => shared}/components/ChooseAvatar/ChooseAvatar.styles.js (64%) create mode 100644 client/src/shared/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.jsx rename client/src/{components/RegistrationPipeline/components/RegistrationForms/AvatarForm => shared}/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.styles.js (95%) create mode 100644 client/src/utils/checkUserStatus.js delete mode 100644 client/src/utils/getServedProfilepic.js diff --git a/client/src/api/hooks/shared/useUpdateAvatar.js b/client/src/api/hooks/shared/useUpdateAvatar.js index 1861b3d89..7222be5b6 100644 --- a/client/src/api/hooks/shared/useUpdateAvatar.js +++ b/client/src/api/hooks/shared/useUpdateAvatar.js @@ -18,6 +18,8 @@ export const useUpdateAvatar = (type) => { onSuccess: async () => { if (type === 'teams') { await queryClient.invalidateQueries('getTeamById', { refetchInactive: true }) + } else if (type === 'users') { + await queryClient.invalidateQueries('getUserById', { refetchInactive: true }) } await queryClient.invalidateQueries('checkAuth', { refetchInactive: true }) diff --git a/client/src/api/hooks/user/useEditUserDetails.js b/client/src/api/hooks/user/useEditUserDetails.js index 1e7bb3b56..7787003db 100644 --- a/client/src/api/hooks/user/useEditUserDetails.js +++ b/client/src/api/hooks/user/useEditUserDetails.js @@ -14,9 +14,12 @@ export const useEditUserDetails = (successHandler) => { return useMutation(finishRegistration, { mutationKey: 'finishRegistration', - onSuccess: async (data) => { - await queryClient.invalidateQueries('checkAuth', { refetchInactive: true }) - successHandler() + onSuccess: (data) => { + queryClient.invalidateQueries('checkAuth', { refetchInactive: true }) + queryClient.invalidateQueries('getUserById', { refetchInactive: true }) + if (successHandler) { + successHandler() + } }, onError: (error) => { // set error message diff --git a/client/src/components/NavBar/Profile/Profile.jsx b/client/src/components/NavBar/Profile/Profile.jsx index d8fdfb7ee..5297d2417 100644 --- a/client/src/components/NavBar/Profile/Profile.jsx +++ b/client/src/components/NavBar/Profile/Profile.jsx @@ -14,6 +14,8 @@ let defaultData = { } const changeData = (data) => { + console.log(data) + return { userRealName: data.fullName, userUsername: data.username, diff --git a/client/src/components/Profile/Profile.jsx b/client/src/components/Profile/Profile.jsx index 6f8280a93..5a1fa2d65 100644 --- a/client/src/components/Profile/Profile.jsx +++ b/client/src/components/Profile/Profile.jsx @@ -1,41 +1,35 @@ -import { useNavigate, useParams } from 'react-router-dom' -import { Form, Formik } from 'formik' +import { useEffect, useState } from 'react' +import { useParams } from 'react-router-dom' +import { Formik } from 'formik' import { useCheckAuth } from '../../api/hooks/auth/useCheckAuth' +import { useUpdateAvatar } from '../../api/hooks/shared/useUpdateAvatar' import { useEditUserDetails } from '../../api/hooks/user/useEditUserDetails' import { useGetUserById } from '../../api/hooks/user/useGetUserById' import PlatformLogo from '../../assets/Platform/TeameightsLogo' -import ROUTES from '../../constants/routes' import { usePrompt } from '../../hooks/usePrompt' -import { LOCAL_PATH } from '../../http' import { editProfileValidation } from '../../schemas' -import { LogoContainer } from '../../shared/components/AppHeader/AppHeader.styles' -import CustomButton from '../../shared/components/CustomButton/CustomButton' -import CustomInput from '../../shared/components/Formik/CustomInput/CustomInput' -import CustomSelect from '../../shared/components/Formik/CustomSelect/CustomSelect' -import CustomTextArea from '../../shared/components/Formik/CustomTextArea/CustomTextArea' import Loader from '../../shared/components/Loader/Loader' -import { Button } from '../../shared/styles/Button.styles' -import { ErrorMessage } from '../../shared/styles/Tpography.styles' -import { calculateAge } from '../../utils/calculateAge' import Page404Form from '../Forms/Page404Form/Page404Form' import ProfileInfo from './components/ProfileInfo/ProfileInfo' import ResumeInfo from './components/ResumeInfo/ResumeInfo' -import { LogoWrapper, ProfileContainer, ProfileWrapper } from './Profile.styles' +import { LogoWrapper, ProfileContainer, ProfileForm, ProfileWrapper } from './Profile.styles' const Profile = () => { - const navigate = useNavigate() const { id } = useParams() const { mutate: editUserDetails, isLoading } = useEditUserDetails() + const { mutate: updateAvatar, isLoading: isUpdatingAvatar } = useUpdateAvatar('users') const { data, isLoading: isUserLoading, error } = useGetUserById(id) const { data: currentUser, isFetching } = useCheckAuth() + const [isEditing, setIsEditing] = useState('') + const [showingUser, setShowingUser] = useState(null) - const showingUser = data?.data + useEffect(() => { + setShowingUser(data?.data) + }, [data]) - const teamSearchHandler = () => { - navigate('/teams') - } + // const showingUser = data?.data const handleSubmit = (values) => { const { @@ -49,6 +43,7 @@ const Profile = () => { linkedIn, programmingLanguages, frameworks, + file, } = values const modifiedUserData = { email: showingUser.email, @@ -67,14 +62,20 @@ const Profile = () => { } editUserDetails(modifiedUserData) + + if (file) { + updateAvatar({ email: showingUser?.email, image: file.split(',')[1] }) + } + + setIsEditing('') } - if (isLoading || isUserLoading || isFetching) { + if (isLoading || isUserLoading || isFetching || isUpdatingAvatar) { return } - if ((!isUserLoading && !showingUser) || error) { - return + if ((!isUserLoading && !data && !isFetching) || error) { + return } /* If not, check if current showingUser has team and if team members have current id in array */ @@ -92,25 +93,38 @@ const Profile = () => { experience: showingUser?.experience, programmingLanguages: showingUser?.programmingLanguages, frameworks: showingUser?.frameworks, + dateOfBirth: showingUser?.dateOfBirth, + file: null, }} validationSchema={editProfileValidation} + enableReinitialize={true} onSubmit={handleSubmit} > {({ values, errors, dirty }) => { usePrompt('You have unsaved changes. Do you want to discard them?', dirty) return ( -
    + + + + - - - - - + + - +
    ) }} diff --git a/client/src/components/Profile/Profile.styles.js b/client/src/components/Profile/Profile.styles.js index c8a8b5be7..4e8af754f 100644 --- a/client/src/components/Profile/Profile.styles.js +++ b/client/src/components/Profile/Profile.styles.js @@ -1,33 +1,44 @@ +import { Form } from 'formik' import styled from 'styled-components' -export const ProfileWrapper = styled.div` +export const ProfileForm = styled(Form)` + width: 100%; + min-height: 100dvh; + padding-left: 88px; + background: #26292b; display: flex; flex-direction: column; justify-content: center; align-items: center; - min-height: 100dvh; - padding-left: 88px; - background: #26292b; - position: relative; @media (max-width: 768px) { padding-left: 0; } ` +export const ProfileWrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + width: 100%; +` + export const ProfileContainer = styled.div` display: flex; - max-width: 800px; + /* max-width: 800px; */ gap: 30px; margin: 0 auto; padding: 0 25px; + width: 100%; + justify-content: center; + align-items: center; @media (max-width: 1024px) { flex-direction: column; align-items: center; margin: 114px 0 24px 0; - max-width: 600px; - width: 100%; } @media (max-width: 768px) { @@ -38,7 +49,7 @@ export const ProfileContainer = styled.div` export const ProfileSection = styled.div` display: flex; flex-direction: column; - align-items: ${(props) => props.align || 'normal'}; + align-items: ${(props) => props.align || 'start'}; gap: ${(props) => props.gap || '0'}; width: ${(props) => props.width || 'auto'}; height: 600px; diff --git a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx index 46bfcc74d..d5c0ffd23 100644 --- a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx +++ b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx @@ -1,40 +1,37 @@ -import { ThreeDots } from 'react-loader-spinner' import { useNavigate } from 'react-router-dom' +import { useFormikContext } from 'formik' import { useInviteUser } from '../../../../api/hooks/team/useInviteUser' -import LongArrowLeft from '../../../../assets/Arrows/LongArrowLeft' import BehanceIcon from '../../../../assets/Links/BehanceIcon' import GitHubIcon from '../../../../assets/Links/GitHubIcon' import LinkedInIcon from '../../../../assets/Links/LinkedInIcon' import TelegramIcon from '../../../../assets/Links/TelegramIcon' -import AddUserIcon from '../../../../assets/Shared/AddUserIcon' import CakeIcon from '../../../../assets/UserProfile/Cake' import EditIcon from '../../../../assets/UserProfile/EditIcon' import EmailIcon from '../../../../assets/UserProfile/EmailIcon' import LocationIcon from '../../../../assets/UserProfile/LocationIcon' -import MessageIcon from '../../../../assets/UserProfile/MessageIcon' import StarIcon from '../../../../assets/UserProfile/StarIcon' import UserIcon from '../../../../assets/UserProfile/UserIcon' import { useGetScreenWidth } from '../../../../hooks/useGetScreenWidth' import FlexWrapper from '../../../../shared/components/FlexWrapper/FlexWrapper' -import { infoToaster } from '../../../../shared/components/Toasters/Info.toaster' import { calculateAge } from '../../../../utils/calculateAge' +import { checkUserStatus } from '../../../../utils/checkUserStatus' import { truncateString } from '../../../../utils/truncateString' import { ProfileSection } from '../../Profile.styles' +import UserStatusButtons from './UserStatusButtons/UserStatusButtons' import { AvatarImg, AvatarWrapper, EditButton, InfoList, InfoListItem, - MessageBtn, SocialList, Text, UserInfo, } from './ProfileInfo.styles' -const ProfileInfo = ({ showingUser, id, currentUser }) => { +const ProfileInfo = ({ showingUser, id, currentUser, isEditing, setIsEditing }) => { const width = useGetScreenWidth() const navigate = useNavigate() const { mutate: inviteUser, isLoading: isInviting } = useInviteUser() @@ -67,17 +64,6 @@ const ProfileInfo = ({ showingUser, id, currentUser }) => { showingUser?.links?.linkedIn && { icon: , link: showingUser?.links?.linkedIn }, ].filter(Boolean) - /* Check if current showingUserId is the same as passed in params */ - const checkUserStatus = () => { - if (currentUser?._id === id) { - return 'same' - } else if (currentUser?.team && currentUser?.team?.members.some((member) => member === id)) { - return 'teammember' - } else { - return 'other' - } - } - const handleInvite = () => { const details = { email: showingUser.email, @@ -88,14 +74,27 @@ const ProfileInfo = ({ showingUser, id, currentUser }) => { inviteUser(details) } + const handleEdit = (target) => { + if (!isEditing) { + setIsEditing(target) + } else { + setIsEditing('') + } + } + + const { values } = useFormikContext() + const userStatus = checkUserStatus(currentUser, id) + return ( - - - - + + {userStatus === 'same' && ( + handleEdit('avatar')}> + + + )} {showingUser?.fullName} @@ -103,57 +102,16 @@ const ProfileInfo = ({ showingUser, id, currentUser }) => { @{showingUser?.username}
    - {checkUserStatus() === 'same' && ( - - This is your profile - - )} - {checkUserStatus() === 'teammember' && ( - - navigate(-1)}> - - Back - - infoToaster('Coming in the next update!')} - background="#46A11B" - > - Message - - - - )} - {checkUserStatus() === 'other' && ( - - {currentUser?.team && ( - - {isInviting ? ( - - ) : ( - <> - Invite - - - )} - - )} - - infoToaster('Coming in the next update!')}> - Message - - - - )} + {infoListArr.map((item, index) => ( diff --git a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js index d0f522fd0..ffebad93d 100644 --- a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js +++ b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.styles.js @@ -17,6 +17,7 @@ export const AvatarImg = styled.img` border-radius: 50%; width: 100px; height: 100px; + user-select: none; ` export const Text = styled.p` @@ -25,7 +26,7 @@ export const Text = styled.p` color: ${(props) => props.color || '#fff'}; ; ` -export const MessageBtn = styled.button` +export const GenericButton = styled.button` height: 40px; cursor: pointer; display: flex; diff --git a/client/src/components/Profile/components/ProfileInfo/UserStatusButtons/UserStatusButtons.jsx b/client/src/components/Profile/components/ProfileInfo/UserStatusButtons/UserStatusButtons.jsx new file mode 100644 index 000000000..8d3780ee1 --- /dev/null +++ b/client/src/components/Profile/components/ProfileInfo/UserStatusButtons/UserStatusButtons.jsx @@ -0,0 +1,105 @@ +import React from 'react' +import { ThreeDots } from 'react-loader-spinner' + +import LongArrowLeft from '../../../../../assets/Arrows/LongArrowLeft' +import AddUserIcon from '../../../../../assets/Shared/AddUserIcon' +import MessageIcon from '../../../../../assets/UserProfile/MessageIcon' +import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' +import { infoToaster } from '../../../../../shared/components/Toasters/Info.toaster' +import { GenericButton } from '../ProfileInfo.styles' + +const UserStatusButtons = ({ + currentUser, + id, + isEditing, + handleEdit, + navigate, + handleInvite, + isInviting, + userStatus, +}) => { + const renderSameUserButtons = () => { + if (isEditing && isEditing !== 'avatar') { + return ( + + + Save changes + + handleEdit('')} + > + Cancel + + + ) + } + + return ( + handleEdit('profile')}> + Edit section + + ) + } + + const renderTeamMemberButtons = () => ( + + navigate(-1)}> + + Back + + infoToaster('Coming in the next update!')} + background="#46A11B" + > + Message + + + + ) + + const renderOtherUserButtons = () => ( + + {currentUser?.team && ( + + {isInviting ? ( + + ) : ( + <> + Invite + + + )} + + )} + infoToaster('Coming in the next update!')}> + Message + + + + ) + + return ( + <> + {userStatus === 'same' && renderSameUserButtons()} + + {userStatus === 'teammember' && renderTeamMemberButtons()} + + {userStatus === 'other' && renderOtherUserButtons()} + + ) +} + +export default UserStatusButtons diff --git a/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentAvatar.jsx b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentAvatar.jsx new file mode 100644 index 000000000..dbeda5434 --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentAvatar.jsx @@ -0,0 +1,24 @@ +import ChooseAvatar from '../../../../../shared/components/ChooseAvatar/ChooseAvatar' +import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' +import { ActionButton, Text } from '../ResumeInfo.styles' + +function EditingComponentAvatar({ handleCancel }) { + return ( + + + Avatar + + + + + Cancel + + + Save + + + + ) +} + +export default EditingComponentAvatar diff --git a/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDefault.jsx b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDefault.jsx new file mode 100644 index 000000000..586374ddb --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDefault.jsx @@ -0,0 +1,23 @@ +import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' +import EducationWork from '../EducationWork/EducationWork' +import ProjectsSkills from '../ProjectsSkills/ProjectsSkills' +import { ResumePartBox, ResumePartBtn } from '../ResumeInfo.styles' + +function EditingComponentDefault({ active, setActive, showingUser }) { + return ( + + + setActive('projects')}> + Projects & Skills + + setActive('education')}> + Education & Work + + + {active === 'projects' && } + {active === 'education' && } + + ) +} + +export default EditingComponentDefault diff --git a/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProfile.jsx b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProfile.jsx new file mode 100644 index 000000000..0a2313ede --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProfile.jsx @@ -0,0 +1,62 @@ +import { useMemo, useState } from 'react' +import { useFormikContext } from 'formik' + +import { countries } from '../../../../../constants/countries' +import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' +import CustomInput from '../../../../../shared/components/Formik/CustomInput/CustomInput' +import CustomSelect from '../../../../../shared/components/Formik/CustomSelect/CustomSelect' +import { ResumePartBox, ResumePartBtn } from '../ResumeInfo.styles' + +function EditingComponentProfile() { + const [isActive, setIsActive] = useState('general') + const { values } = useFormikContext() + + return ( + + + setIsActive('general')}> + General + + setIsActive('concentration')} + > + Concentration + + setIsActive('links')}> + Links + + + + {/* */} + + + + ) +} + +export default EditingComponentProfile diff --git a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx index d6bfaa02f..1da29fef5 100644 --- a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx +++ b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.jsx @@ -1,29 +1,52 @@ import { useState } from 'react' +import { useFormikContext } from 'formik' -import FlexWrapper from '../../../../shared/components/FlexWrapper/FlexWrapper' import { ProfileSection } from '../../Profile.styles' -import EducationWork from './EducationWork/EducationWork' -import ProjectsSkills from './ProjectsSkills/ProjectsSkills' -import { ResumePartBox, ResumePartBtn } from './ResumeInfo.styles' +import EditingComponentAvatar from './EditingComponents/EditingComponentAvatar' +import EditingComponentDefault from './EditingComponents/EditingComponentDefault' +import EditingComponentProfile from './EditingComponents/EditingComponentProfile' -const ResumeInfo = ({ showingUser }) => { +const ResumeInfo = ({ showingUser, isEditing, setIsEditing }) => { const [active, setActive] = useState('projects') + const { setFieldValue } = useFormikContext() + + const handleCancel = () => { + setIsEditing('') + setFieldValue('file', null) + } + + const currentData = () => { + let content = null + + switch (isEditing) { + case '': + content = ( + + ) + break + case 'avatar': + content = + break + case 'profile': + content = + break + + default: + content = null + break + } + + return content + } return ( - - - setActive('projects')}> - Projects & Skills - - setActive('education')}> - Education & Work - - - {active === 'projects' && } - {active === 'education' && } - + {currentData()} ) } diff --git a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js index 1185631f2..99609da81 100644 --- a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js +++ b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js @@ -19,6 +19,8 @@ export const ResumePartBtn = styled.div` cursor: pointer; color: ${(props) => (props.isActive ? '#5BD424' : '#fff')}; transition: opacity 0.3s; + font-size: 20px; + font-weight: 500; &:hover { opacity: 0.6; } @@ -29,12 +31,16 @@ export const ResumePartBtn = styled.div` content: ''; position: absolute; left: 0; - bottom: -4px; + bottom: 0; height: 1px; background-color: #5bd424; width: 100%; } `} + + @media screen and (max-width: 600px) { + font-size: 18px; + } ` export const WrappableList = styled.ul` @@ -61,3 +67,16 @@ export const LanguageItem = styled.li` justify-content: center; align-items: center; ` + +export const ActionButton = styled.button` + outline: none; + background: ${(props) => props.background || 'transparent'}; + border-radius: 10px; + border: ${(props) => props.border || '2px solid #A5211F'}; + color: white; + font-size: 16px; + font-weight: 400; + width: 83px; + height: 32px; + cursor: pointer; +` diff --git a/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/AvatarForm.jsx b/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/AvatarForm.jsx index dd355c203..4d6467d12 100644 --- a/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/AvatarForm.jsx +++ b/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/AvatarForm.jsx @@ -5,11 +5,11 @@ import { ContentContainer } from '../../MultiStepRegistration/MultiStepRegistrat import AvatarSelection from './components/AvatarSelection/AvatarSelection' import { AvatarFormText } from './AvatarForm.styles' -const AvatarForm = ({ text, defaultAvatars }) => { +const AvatarForm = ({ text }) => { return ( {text} - + ) } diff --git a/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/AvatarSelection/AvatarSelection.jsx b/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/AvatarSelection/AvatarSelection.jsx index 2050559d0..d339d6b68 100644 --- a/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/AvatarSelection/AvatarSelection.jsx +++ b/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/AvatarSelection/AvatarSelection.jsx @@ -1,17 +1,17 @@ import { useFormikContext } from 'formik' -import ChooseAvatar from '../ChooseAvatar/ChooseAvatar' +import ChooseAvatar from '../../../../../../../shared/components/ChooseAvatar/ChooseAvatar' import { Avatar, AvatarSelectionContainer } from './AvatarSelection.styles' -const AvatarSelection = ({ defaultAvatars }) => { +const AvatarSelection = () => { const { getFieldProps } = useFormikContext() const currentAvatar = getFieldProps('file').value return ( - + ) } diff --git a/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.jsx b/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.jsx deleted file mode 100644 index a5e586b72..000000000 --- a/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { useState } from 'react' -import Dropzone from 'react-dropzone' - -import { ReactComponent as UploadAvatarIcon } from '../../../../../../../../../assets/Avatars/uploadAvatarIcon.svg' - -import { DropzoneContent, DropzoneText } from './CustomDropZone.styles' - -const CustomDropZone = ({ setUserAvatar }) => { - function handleDrop(acceptedFiles) { - const file = acceptedFiles[0] - const reader = new FileReader() - - reader.readAsDataURL(file) - reader.onload = function () { - const imageUrl = reader.result - - setUserAvatar(imageUrl) - } - } - - return ( - - {({ getRootProps, getInputProps }) => ( -
    - - - - Drop here or click to upload - -
    - )} -
    - ) -} - -export default CustomDropZone diff --git a/client/src/components/RegistrationPipeline/index.js b/client/src/components/RegistrationPipeline/index.js index 301ed915f..a31d70643 100644 --- a/client/src/components/RegistrationPipeline/index.js +++ b/client/src/components/RegistrationPipeline/index.js @@ -5,7 +5,6 @@ import { useNavigate } from 'react-router-dom' import { useUpdateAvatar } from '../../api/hooks/shared/useUpdateAvatar' import { useEditUserDetails } from '../../api/hooks/user/useEditUserDetails' -import { defaultUserAvatars } from '../../constants/finishRegistrationData' import { finishRegistrationValidation } from '../../schemas' import { setIsFinishRegistrationStarted, setStep } from '../../store/reducers/RegistrationAuth' import { formatDateString } from '../../utils/convertStringToDate' @@ -39,10 +38,7 @@ function FinishRegistration() { { component: , name: 'Links', isOptional: true }, { component: ( - + ), name: 'Avatar', isOptional: true, diff --git a/client/src/components/Search/Search.js b/client/src/components/Search/Search.js index 6d02b0cc6..8cd45b45a 100644 --- a/client/src/components/Search/Search.js +++ b/client/src/components/Search/Search.js @@ -1,8 +1,7 @@ -import { useMemo, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import countryList from 'react-select-country-list' import concentrationOptions from '../../constants/concentrations' +import { countries } from '../../constants/countries' import frameworkOptions from '../../constants/frameworks' import { programmingLanguageOptions } from '../../constants/programmingLanguages' @@ -17,8 +16,6 @@ const Search = ({ sliceName, setFilterValueAction, currFilterIndex }) => { const currFilter = filtersArr[currFilterIndex] - const countries = useMemo(() => countryList().getData(), []) - switch (currFilter.type) { case 'text': return ( diff --git a/client/src/components/Team/EditImage/EditImage.jsx b/client/src/components/Team/EditImage/EditImage.jsx deleted file mode 100644 index 791bf9040..000000000 --- a/client/src/components/Team/EditImage/EditImage.jsx +++ /dev/null @@ -1,188 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { Field, Form } from 'formik' - -import { CheckCircle } from '../../../assets/Team/CheckCircle' -import { UploadSymbol } from '../../../assets/Team/UploadSymbol' -import FlexWrapper from '../../../shared/components/FlexWrapper/FlexWrapper' - -import { - DefaultImg, - DropFileArea, - FileButton, - ImageBox, - MyRadioGroup, - Text, -} from './EditImage.styles' - -const EditImage = ({ - selectedImage, - imgData, - picture, - setImgData, - setPicture, - changeSelectedImage, - defaultTeamImages, -}) => { - const [errorMessage, updateErrorMessage] = useState('') - - useEffect(() => { - if (errorMessage === '') { - return - } - - const timer = () => { - updateErrorMessage('') - } - - const timeout = setTimeout(timer, 2000) - - return () => clearTimeout(timeout) - }, [errorMessage]) - - const selectedImgJSX = - imgData === null ? ( - <> - - - {errorMessage.length > 0 ? errorMessage : 'Drop here or click to upload'} - - - ) : ( -
    - {picture === null ? '' : picture.name} -
    - ) - - return ( -
    - - - Select a default - { - const pic = e.target.dataset.pic - - if (pic === undefined) { - return - } - const nextPic = pic === selectedImage ? '' : pic - - setPicture(null) - setImgData(null) - changeSelectedImage(nextPic) - }} - > - {defaultTeamImages.map((image, key) => ( - - - - - - - ))} - - - - - - Or add your own - { - ev.preventDefault() - const file = ev.target.files[0] - - const fileExtension = file.name.split('.').pop().toLowerCase() - - // check file type - if ( - !file.type.includes('jpeg') && - !fileExtension.includes('png') && - !fileExtension.includes('jpg') - ) { - updateErrorMessage('Only JPEG, JPG and PNG formats are accepted.') - - return - } - - if (file.size > 10000000) { - updateErrorMessage('Image too big') - - return - } - - setPicture(file) - const reader = new FileReader() - - reader.addEventListener('load', () => { - changeSelectedImage('') - setImgData(reader.result) - }) - reader.readAsDataURL(file) - }} - /> - { - ev.preventDefault() - document.querySelector('#image').click() - }} - onDrop={(ev) => { - ev.preventDefault() - - if (ev.dataTransfer.items) { - // Use DataTransferItemList interface to access the file(s) - ;[...ev.dataTransfer.items].forEach((item, i) => { - // If dropped items aren't files, reject them - if (item.kind === 'file') { - const file = item.getAsFile() - - const fileExtension = file.name.split('.').pop().toLowerCase() - - // check file type - if ( - !file.type.includes('jpeg') && - !fileExtension.includes('png') && - !fileExtension.includes('jpg') - ) { - updateErrorMessage('Only JPEG, JPG and PNG formats are accepted.') - - return - } - - setPicture(file) - const reader = new FileReader() - - reader.readAsDataURL(file) - reader.addEventListener('load', () => { - changeSelectedImage('') - setImgData(reader.result) - }) - } - }) - } else { - // Use DataTransfer interface to access the file(s) - ;[...ev.dataTransfer.files].forEach((file, i) => { - // console.log(`… file[${i}].name = ${file.name}`) - }) - } - }} - onDragOver={(e) => { - e.preventDefault() - document.querySelector('#image').click() - }} - dropzone="move" - > - {selectedImgJSX} - - -
    - ) -} - -export default EditImage diff --git a/client/src/components/Team/EditImage/EditImage.styles.js b/client/src/components/Team/EditImage/EditImage.styles.js deleted file mode 100644 index 8fea1fc74..000000000 --- a/client/src/components/Team/EditImage/EditImage.styles.js +++ /dev/null @@ -1,91 +0,0 @@ -import styled from 'styled-components' - -export const MyRadioGroup = styled.div` - display: flex; - justify-content: start; - align-items: center; - gap: 15px; - margin-bottom: 24px; - - @media screen and (max-width: 660px) { - overflow-x: scroll; - - ::-webkit-scrollbar { - /* WebKit */ - transition: all 0.2s; - width: 5px; - height: 5px; - } - ::-webkit-scrollbar-track { - background: transparent; - } - - ::-webkit-scrollbar-thumb { - background-color: #5d9d0b; - border-radius: 10px; - } - } -` - -export const FileButton = styled.div` - border: 2px dashed #86878b; - border-radius: 10px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 0; - cursor: pointer; - height: 184px; -` - -export const ImageBox = styled.div` - position: relative; - opacity: ${(props) => (props.myKey ? '1' : '.50')}; - transition: all 0.2s; - span { - display: block; - position: absolute; - svg { - width: 14.29px; - height: 14.29px; - } - // background-color: green; - right: 0%; - bottom: 0%; - opacity: ${(props) => (props.myKey ? '1' : '0')}; - } -` - -export const DefaultImg = styled.img` - width: 50px; - height: 50px; - cursor: pointer; - border-radius: 50%; - &:after { - display: block; - content: '?'; - width: 20px; - height: 20px; - background-color: green; - position: absolute; - } -` - -export const Text = styled.h3` - font-weight: ${(props) => props.fontWeight || '400'}; - font-size: ${(props) => props.fontSize || '16px'}; - color: ${(props) => props.color || '#fff'}; - margin: ${(props) => props.margin || '0'}; - /* line-height: ${(props) => props.lineHeight || '1'}; */ -` - -export const DropFileArea = styled.input` - color: #fff; - border: none; - border-bottom: 1px solid #86878b; - transition: all 0.2s; - position: absolute; - opacity: 0; - pointer-events: none; -` diff --git a/client/src/components/Team/TeamForm/ActionButtonsType.js b/client/src/components/Team/TeamForm/ActionButtonsType.js index 9758a548a..9f5382d26 100644 --- a/client/src/components/Team/TeamForm/ActionButtonsType.js +++ b/client/src/components/Team/TeamForm/ActionButtonsType.js @@ -6,19 +6,15 @@ const ActionButtonsType = ({ team, isEditing, setIsEditing, - editImage, handleOpenDelete, handleOpenLeave, updateTeamsAvatar, - servedProfilePic, - picture, - selectedImage, role, handleJoin, updateTeam, }) => { let action = null - const { values } = useFormikContext() + const { values, setFieldValue } = useFormikContext() switch (role) { case 'leader': @@ -38,8 +34,8 @@ const ActionButtonsType = ({ updateTeam(updateTeamObj) - if (editImage && (picture || selectedImage !== '')) { - updateTeamsAvatar({ teamID: team._id, image: servedProfilePic.split(',')[1] }) + if (values?.file) { + updateTeamsAvatar({ teamID: team._id, image: values?.file?.split(',')[1] }) } } setIsEditing((prevState) => !prevState) @@ -53,6 +49,7 @@ const ActionButtonsType = ({ onClick={() => { if (isEditing) { setIsEditing((prevState) => !prevState) + setFieldValue('file', null) } else { handleOpenDelete() } diff --git a/client/src/components/Team/TeamForm/TeamForm.js b/client/src/components/Team/TeamForm/TeamForm.js index 53d65400f..b59cafb36 100644 --- a/client/src/components/Team/TeamForm/TeamForm.js +++ b/client/src/components/Team/TeamForm/TeamForm.js @@ -18,7 +18,6 @@ import ROUTES from '../../../constants/routes' import { editTeamValidation } from '../../../schemas' import Loader from '../../../shared/components/Loader/Loader' import { determineUserRoleInTeam } from '../../../utils/determineUserRoleInTeam' -import { getServedProfilePic } from '../../../utils/getServedProfilepic' import Page404Form from '../../Forms/Page404Form/Page404Form' import TeamModal from '../Modal/TeamModal' import { TeamProfileLargeCard } from '../TeamProfileLargeCard/TeamProfileLargeCard' @@ -68,18 +67,6 @@ function TeamForm() { } }, [isEditing]) - const [selectedImage, changeSelectedImage] = useState('') - const [picture, setPicture] = useState(null) - const [imgData, setImgData] = useState(null) - - useEffect(() => { - setPicture(null) - setImgData(null) - changeSelectedImage('') - }, [isEditing]) - - const servedProfilePic = getServedProfilePic(selectedImage, picture, imgData) - // handleClose() function const handleClose = () => { setOpen(false) @@ -164,9 +151,6 @@ function TeamForm() { handleOpenDelete={handleOpenDelete} handleOpenLeave={handleOpenLeave} updateTeamsAvatar={updateTeamsAvatar} - servedProfilePic={servedProfilePic} - picture={picture} - selectedImage={selectedImage} role={role} handleJoin={handleJoin} updateTeam={updateTeam} @@ -183,8 +167,7 @@ function TeamForm() { '', ), description: team?.description, - image: '', - default: '', + file: null, }} validationSchema={editTeamValidation} > @@ -226,25 +209,16 @@ function TeamForm() { handleOpenDelete={handleOpenDelete} switchIsMembers={switchIsMembers} handleOpenInvite={handleOpenInvite} - selectedImage={selectedImage} - setImgData={setImgData} - setPicture={setPicture} - changeSelectedImage={changeSelectedImage} - imgData={imgData} - picture={picture} role={role} handleOpenTransferLeader={handleOpenTransferLeader} /> diff --git a/client/src/components/Team/TeamProfileLargeCard/TeamProfileLargeCard.jsx b/client/src/components/Team/TeamProfileLargeCard/TeamProfileLargeCard.jsx index 7323f2654..cdb7d6d62 100644 --- a/client/src/components/Team/TeamProfileLargeCard/TeamProfileLargeCard.jsx +++ b/client/src/components/Team/TeamProfileLargeCard/TeamProfileLargeCard.jsx @@ -1,6 +1,5 @@ -import { defaultTeamImages } from '../../../constants/images' +import ChooseAvatar from '../../../shared/components/ChooseAvatar/ChooseAvatar' import About from '../About/About' -import EditImage from '../EditImage/EditImage' import LargeCardSwitch from '../LargeCardSwitch/LargeCardSwitch' import Members from '../Members/Members' @@ -16,12 +15,6 @@ export const TeamProfileLargeCard = ({ handleOpenDelete, switchIsMembers, handleOpenInvite, - selectedImage, - setImgData, - setPicture, - changeSelectedImage, - imgData, - picture, role, handleOpenTransferLeader, }) => { @@ -78,17 +71,7 @@ export const TeamProfileLargeCard = ({ )} - {editImage && ( - - )} + {editImage && } ) } diff --git a/client/src/components/Team/TeamProfileMiniCard/TeamProfileMiniCard.jsx b/client/src/components/Team/TeamProfileMiniCard/TeamProfileMiniCard.jsx index 2c2bed5b6..ce71fbda0 100644 --- a/client/src/components/Team/TeamProfileMiniCard/TeamProfileMiniCard.jsx +++ b/client/src/components/Team/TeamProfileMiniCard/TeamProfileMiniCard.jsx @@ -1,9 +1,9 @@ +import { useFormikContext } from 'formik' + import Cake from '../../../assets/Team/Cake' -import Crown from '../../../assets/Team/Crown' import { PencilSimple } from '../../../assets/Team/PencilSimple' import Users from '../../../assets/Team/Users' import { B2fs, B2fw, B2lh, H4fs, H4fw, H4lh } from '../../../constants/fonts' -import { LOCAL_PATH } from '../../../http' import FlexWrapper from '../../../shared/components/FlexWrapper/FlexWrapper' import { Text } from '../TeamForm/TeamForm.styles' @@ -16,16 +16,9 @@ import { TeamInformationContainer, } from './TeamProfileMiniCard.styles' -const TeamProfileMiniCard = ({ - team, - picture, - selectedImage, - isEditing, - setEditImage, - actionType, - servedProfilePic, - editImage, -}) => { +const TeamProfileMiniCard = ({ team, isEditing, setEditImage, actionType, editImage }) => { + const { values } = useFormikContext() + return ( <> @@ -34,7 +27,7 @@ const TeamProfileMiniCard = ({
    {isEditing ? ( { - const [defaultAvatarSelected, setDefaultAvatarSelected] = useState(defaultAvatars[0]) - const [userAvatar, setUserAvatar] = useState(null) +const ChooseAvatar = ({ type = 'user' }) => { + const [defaultAvatarSelected, setDefaultAvatarSelected] = useState( + type === 'user' ? defaultUserAvatars[0] : defaultTeamAvatars[0], + ) + const [avatar, setAvatar] = useState(null) const { setFieldValue } = useFormikContext() const onDefaultAvatarSelect = (defaultAvatar) => { setDefaultAvatarSelected(defaultAvatar) - if (userAvatar) { - setUserAvatar(null) + if (avatar) { + setAvatar(null) } } useEffect(() => { - if (userAvatar) { - setFieldValue('file', userAvatar) + if (avatar) { + setFieldValue('file', avatar) setDefaultAvatarSelected(null) } else { setFieldValue('file', defaultAvatarSelected?.path) } - }, [userAvatar, defaultAvatarSelected]) + }, [avatar, defaultAvatarSelected]) + + const avatars = type === 'user' ? defaultUserAvatars : defaultTeamAvatars return ( Select one of default - {defaultAvatars.map((avatar) => ( + {avatars?.map((avatar) => ( onDefaultAvatarSelect(avatar)} key={avatar.name} @@ -46,7 +53,7 @@ const ChooseAvatar = ({ defaultAvatars }) => { ))} Or add your own - + ) } diff --git a/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/ChooseAvatar/ChooseAvatar.styles.js b/client/src/shared/components/ChooseAvatar/ChooseAvatar.styles.js similarity index 64% rename from client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/ChooseAvatar/ChooseAvatar.styles.js rename to client/src/shared/components/ChooseAvatar/ChooseAvatar.styles.js index af1199911..faab56522 100644 --- a/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/ChooseAvatar/ChooseAvatar.styles.js +++ b/client/src/shared/components/ChooseAvatar/ChooseAvatar.styles.js @@ -1,14 +1,36 @@ import styled, { css } from 'styled-components' -import selectedIcon from '../../../../../../../assets/Avatars/defaultAvatarSelectedIcon.svg' +import selectedIcon from '../../../assets/Avatars/defaultAvatarSelectedIcon.svg' export const ChooseAvatarContainer = styled.div` flex: 1; + width: 100%; ` export const DefaultAvatarList = styled.ul` display: flex; gap: 0.75rem; + width: 100%; + max-width: 406px; + + @media screen and (max-width: 500px) { + overflow-x: scroll; + + ::-webkit-scrollbar { + /* WebKit */ + transition: all 0.2s; + width: 5px; + height: 5px; + } + ::-webkit-scrollbar-track { + background: transparent; + } + + ::-webkit-scrollbar-thumb { + background-color: #5d9d0b; + border-radius: 10px; + } + } ` export const DefaultAvatarContainer = styled.li` diff --git a/client/src/shared/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.jsx b/client/src/shared/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.jsx new file mode 100644 index 000000000..99fc61fbc --- /dev/null +++ b/client/src/shared/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.jsx @@ -0,0 +1,73 @@ +import React, { useCallback, useState } from 'react' +import { useDropzone } from 'react-dropzone' + +import { ReactComponent as UploadAvatarIcon } from '../../../../../assets/Avatars/uploadAvatarIcon.svg' + +import { DropzoneContent, DropzoneText } from './CustomDropZone.styles' + +const MAX_SIZE = 1572864 + +const CustomDropZone = ({ setUserAvatar, defaultAvatarSelected }) => { + const onDrop = useCallback((acceptedFiles) => { + const file = acceptedFiles[0] + + const reader = new FileReader() + + reader.readAsDataURL(file) + reader.onload = function () { + const imageUrl = reader.result + + setUserAvatar(imageUrl) + } + }, []) + + const { + getRootProps, + getInputProps, + isDragActive, + acceptedFiles, + fileRejections, + isFileDialogActive, + } = useDropzone({ + onDrop, + maxFiles: 1, + accept: { + 'image/jpeg': [], + 'image/png': [], + }, + maxSize: MAX_SIZE, + }) + + function renderDropzoneText() { + if (fileRejections[0] && !defaultAvatarSelected) { + return {fileRejections[0]?.errors[0].message} + } + + if (acceptedFiles[0] && !defaultAvatarSelected) { + return {acceptedFiles[0]?.name} + } + + if ( + (!isDragActive && !acceptedFiles?.length && !fileRejections?.length) || + defaultAvatarSelected + ) { + return ( + <> + + Drop here or click to upload + + ) + } + + return null + } + + return ( +
    + + {renderDropzoneText()} +
    + ) +} + +export default CustomDropZone diff --git a/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.styles.js b/client/src/shared/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.styles.js similarity index 95% rename from client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.styles.js rename to client/src/shared/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.styles.js index 3400ac06a..9233b4f73 100644 --- a/client/src/components/RegistrationPipeline/components/RegistrationForms/AvatarForm/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.styles.js +++ b/client/src/shared/components/ChooseAvatar/components/CustomDropZone/CustomDropZone.styles.js @@ -11,6 +11,7 @@ export const DropzoneContent = styled.div` border: 2px dashed #86878b; border-radius: 10px; background: #2f3239; + height: 100px; ` export const DropzoneText = styled.p` diff --git a/client/src/utils/checkUserStatus.js b/client/src/utils/checkUserStatus.js new file mode 100644 index 000000000..19a1257fa --- /dev/null +++ b/client/src/utils/checkUserStatus.js @@ -0,0 +1,10 @@ +export const checkUserStatus = (user, id) => { + if (user?._id === id) { + return 'same' + } + if (user?.team?.members.includes(id)) { + return 'teammember' + } + + return 'other' +} diff --git a/client/src/utils/getServedProfilepic.js b/client/src/utils/getServedProfilepic.js deleted file mode 100644 index ae9ee17b6..000000000 --- a/client/src/utils/getServedProfilepic.js +++ /dev/null @@ -1,12 +0,0 @@ -import { defaultTeamImages } from '../constants/images' - -export const getServedProfilePic = (selectedImage, picture, imgData) => { - // if we have a default, choose default - if (selectedImage !== '') { - return require(`../assets/Images/team/${defaultTeamImages[selectedImage]}.png`) - } - - if (picture !== null) { - return imgData - } -} diff --git a/client/src/utils/truncateString.js b/client/src/utils/truncateString.js index 3fbbebe00..3e49d264f 100644 --- a/client/src/utils/truncateString.js +++ b/client/src/utils/truncateString.js @@ -1,6 +1,6 @@ export const truncateString = (str, screenWidth) => { - if (str.length > 23 && screenWidth > 1024) { - let halfLength = Math.floor((23 - 3) / 2) // Subtracting 3 for the ellipsis + if (str?.length > 21 && screenWidth > 1024) { + let halfLength = Math.floor((21 - 3) / 2) // Subtracting 3 for the ellipsis let firstHalf = str.substr(0, halfLength) let secondHalf = str.substr(str.length - halfLength) From 215bb147d48d9b20bc38491f919a4abc85bcf306 Mon Sep 17 00:00:00 2001 From: Nikita Mashchenko Date: Thu, 29 Jun 2023 21:00:20 -0500 Subject: [PATCH 07/11] Bug fixes & new component --- client/src/assets/Arrows/ArrowDown.js | 11 +- client/src/components/Profile/Profile.jsx | 2 +- .../components/ProfileInfo/ProfileInfo.jsx | 7 +- .../EditingComponentProfile.jsx | 79 ++- .../ProjectsSkills/ProjectsSkills.jsx | 99 ++- .../ResumeInfo/ResumeInfo.styles.js | 33 + .../InviteMembersForm/InviteMembersForm.jsx | 4 +- .../components/RegistrationPipeline/index.js | 4 +- .../src/components/Team/Modal/TeamModal.jsx | 4 +- client/src/constants/countries.js | 665 +++++++++++------- .../src/constants/finishRegistrationData.js | 8 +- .../Formik/CustomInput/CustomInput.styles.js | 1 + .../CustomSelect/CustomSelect.styles.js | 7 +- .../CustomSelectAutocomplete.jsx | 64 ++ .../CustomSelectAutocomplete.theme.js | 76 ++ .../components/Links/Links.jsx} | 12 +- .../shared/components/Links/Links.styles.jsx | 0 .../List.js | 0 .../Loader.js | 2 +- .../SearchUsersAutocomplete.jsx} | 5 +- .../SearchUsersAutocomplete.styles.js} | 0 .../SearchUsersAutocomplete.theme.js} | 0 .../TextField.js | 0 23 files changed, 748 insertions(+), 335 deletions(-) create mode 100644 client/src/shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete.jsx create mode 100644 client/src/shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete.theme.js rename client/src/{components/RegistrationPipeline/components/RegistrationForms/UserLinksForm/UserLinksForm.jsx => shared/components/Links/Links.jsx} (52%) create mode 100644 client/src/shared/components/Links/Links.styles.jsx rename client/src/shared/components/{AutocompleteInput => SearchUsersAutocomplete}/List.js (100%) rename client/src/shared/components/{AutocompleteInput => SearchUsersAutocomplete}/Loader.js (81%) rename client/src/shared/components/{AutocompleteInput/AutocompleteInput.jsx => SearchUsersAutocomplete/SearchUsersAutocomplete.jsx} (93%) rename client/src/shared/components/{AutocompleteInput/AutocompleteInput.styles.js => SearchUsersAutocomplete/SearchUsersAutocomplete.styles.js} (100%) rename client/src/shared/components/{AutocompleteInput/AutocompleteInput.theme.js => SearchUsersAutocomplete/SearchUsersAutocomplete.theme.js} (100%) rename client/src/shared/components/{AutocompleteInput => SearchUsersAutocomplete}/TextField.js (100%) diff --git a/client/src/assets/Arrows/ArrowDown.js b/client/src/assets/Arrows/ArrowDown.js index 6451b89f0..0dc394d68 100644 --- a/client/src/assets/Arrows/ArrowDown.js +++ b/client/src/assets/Arrows/ArrowDown.js @@ -1,12 +1,11 @@ function ArrowDown() { return ( - + ) diff --git a/client/src/components/Profile/Profile.jsx b/client/src/components/Profile/Profile.jsx index 5a1fa2d65..e7c77827d 100644 --- a/client/src/components/Profile/Profile.jsx +++ b/client/src/components/Profile/Profile.jsx @@ -70,7 +70,7 @@ const Profile = () => { setIsEditing('') } - if (isLoading || isUserLoading || isFetching || isUpdatingAvatar) { + if (isLoading || isUserLoading || isFetching || isUpdatingAvatar || !showingUser) { return } diff --git a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx index d5c0ffd23..db202b7d4 100644 --- a/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx +++ b/client/src/components/Profile/components/ProfileInfo/ProfileInfo.jsx @@ -74,17 +74,18 @@ const ProfileInfo = ({ showingUser, id, currentUser, isEditing, setIsEditing }) inviteUser(details) } + const { values, resetForm } = useFormikContext() + const userStatus = checkUserStatus(currentUser, id) + const handleEdit = (target) => { if (!isEditing) { setIsEditing(target) } else { + resetForm() setIsEditing('') } } - const { values } = useFormikContext() - const userStatus = checkUserStatus(currentUser, id) - return ( diff --git a/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProfile.jsx b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProfile.jsx index 0a2313ede..80d3c85f9 100644 --- a/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProfile.jsx +++ b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProfile.jsx @@ -1,10 +1,13 @@ -import { useMemo, useState } from 'react' +import { useState } from 'react' import { useFormikContext } from 'formik' +import concentrationOptions from '../../../../../constants/concentrations' import { countries } from '../../../../../constants/countries' +import { userExperienceOptions } from '../../../../../constants/finishRegistrationData' import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' import CustomInput from '../../../../../shared/components/Formik/CustomInput/CustomInput' -import CustomSelect from '../../../../../shared/components/Formik/CustomSelect/CustomSelect' +import CustomSelectAutocomplete from '../../../../../shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete' +import Links from '../../../../../shared/components/Links/Links' import { ResumePartBox, ResumePartBtn } from '../ResumeInfo.styles' function EditingComponentProfile() { @@ -27,34 +30,50 @@ function EditingComponentProfile() { Links - - {/* */} - - + {isActive === 'general' && ( + <> + + + + + )} + {isActive === 'concentration' && ( + <> + + + + )} + {isActive === 'links' && } ) } diff --git a/client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx b/client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx index 7940b11c7..5eff87a4f 100644 --- a/client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx +++ b/client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx @@ -1,9 +1,17 @@ +import EditIcon from '../../../../../assets/UserProfile/EditIcon' import LinkIcon from '../../../../../assets/UserProfile/LinkIcon' import TeamMembersIcon from '../../../../../assets/UserProfile/TeamMembersIcon' import { frameworkColors, frameworkTextColors } from '../../../../../constants/frameworkColors' import { languageOptions } from '../../../../../constants/programmingLanguages' import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' -import { FrameWorkItem, LanguageItem, Text, WrappableList } from '../ResumeInfo.styles' +import { + EditIconContainer, + FrameWorkItem, + LanguageItem, + Text, + TextArea, + WrappableList, +} from '../ResumeInfo.styles' import TagLink from '../TagLink/TagLink' const ProjectsSkills = ({ showingUser }) => { @@ -17,32 +25,55 @@ const ProjectsSkills = ({ showingUser }) => { return ( - - - About me - - - {showingUser?.description ? showingUser?.description : 'This showingUser is humble'} - + + + + About me + + + + + + {showingUser?.description ? ( + + ) : ( + + )} - - - Team - - {showingUser?.team ? ( + {showingUser?.team ? ( + + + Team + + } to={`/team/${showingUser?.team._id}`}> {showingUser?.team.name} - ) : ( - + + ) : ( + + + Team + + + No team yet. - )} - - - - Projects - + + )} + + + + Projects + + + + + {projectsArr.map(({ link, text }, index) => (
  • @@ -53,10 +84,15 @@ const ProjectsSkills = ({ showingUser }) => { ))} - - - Frameworks - + + + + Frameworks + + + + + {showingUser?.frameworks?.map((framework, index) => ( { ))} - - - Languages - + + + + Languages + + + + + {showingUser?.programmingLanguages?.map((language, index) => ( {languageOptions[language]} diff --git a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js index 99609da81..bbb4c9d28 100644 --- a/client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js +++ b/client/src/components/Profile/components/ResumeInfo/ResumeInfo.styles.js @@ -7,11 +7,31 @@ export const Text = styled.p` line-height: '120%'; ` +export const TextArea = styled.textarea` + font-weight: ${(props) => props.fontWeight || '500'}; + font-size: ${(props) => props.fontSize || '20px'}; + color: ${(props) => props.color || '#fff'}; + height: 85px; + background: #1a1c22; + border: none; + resize: none; +` + export const ResumePartBox = styled.div` height: 32px; display: flex; align-items: center; gap: 16px; + overflow-x: scroll; + + ::-webkit-scrollbar { + /* WebKit */ + width: 0; + height: 0; + } + ::-webkit-scrollbar-track { + background: transparent; + } ` export const ResumePartBtn = styled.div` @@ -21,6 +41,7 @@ export const ResumePartBtn = styled.div` transition: opacity 0.3s; font-size: 20px; font-weight: 500; + white-space: nowrap; &:hover { opacity: 0.6; } @@ -80,3 +101,15 @@ export const ActionButton = styled.button` height: 32px; cursor: pointer; ` + +export const EditIconContainer = styled.div` + cursor: pointer; + + :hover { + svg { + path { + fill: #5bd424; + } + } + } +` diff --git a/client/src/components/RegistrationPipeline/components/RegistrationForms/InviteMembersForm/InviteMembersForm.jsx b/client/src/components/RegistrationPipeline/components/RegistrationForms/InviteMembersForm/InviteMembersForm.jsx index 4adf59b35..dc3157952 100644 --- a/client/src/components/RegistrationPipeline/components/RegistrationForms/InviteMembersForm/InviteMembersForm.jsx +++ b/client/src/components/RegistrationPipeline/components/RegistrationForms/InviteMembersForm/InviteMembersForm.jsx @@ -4,7 +4,7 @@ import { useFormikContext } from 'formik' import AddUserIcon from '../../../../../assets/Shared/AddUserIcon' import DeleteIcon from '../../../../../assets/Shared/DeleteIcon' import { LOCAL_PATH } from '../../../../../http' -import AutocompleteInput from '../../../../../shared/components/AutocompleteInput/AutocompleteInput' +import SearchUsersAutocomplete from '../../../../../shared/components/SearchUsersAutocomplete/SearchUsersAutocomplete' import { errorToaster } from '../../../../../shared/components/Toasters/Error.toaster' import { ContentContainer } from '../../MultiStepRegistration/MultiStepRegistration.styles' @@ -59,7 +59,7 @@ const InviteMembersForm = () => { Team members can be removed as needed. - + Send invite diff --git a/client/src/components/RegistrationPipeline/index.js b/client/src/components/RegistrationPipeline/index.js index a31d70643..031738199 100644 --- a/client/src/components/RegistrationPipeline/index.js +++ b/client/src/components/RegistrationPipeline/index.js @@ -6,6 +6,7 @@ import { useNavigate } from 'react-router-dom' import { useUpdateAvatar } from '../../api/hooks/shared/useUpdateAvatar' import { useEditUserDetails } from '../../api/hooks/user/useEditUserDetails' import { finishRegistrationValidation } from '../../schemas' +import Links from '../../shared/components/Links/Links' import { setIsFinishRegistrationStarted, setStep } from '../../store/reducers/RegistrationAuth' import { formatDateString } from '../../utils/convertStringToDate' import { convertYearToDate } from '../../utils/convertYearToDate' @@ -19,7 +20,6 @@ import UserConcentrationForm from './components/RegistrationForms/UserConcentrat import UserEducationForm from './components/RegistrationForms/UserEducationForm/UserEducationForm' import UserExperienceForm from './components/RegistrationForms/UserExperienceForm/UserExperienceForm' import UserJobForm from './components/RegistrationForms/UserJobForm/UserJobForm' -import UserLinksForm from './components/RegistrationForms/UserLinksForm/UserLinksForm' function FinishRegistration() { const { isFinishRegistrationStarted } = useSelector((state) => state.registrationReducer) @@ -35,7 +35,7 @@ function FinishRegistration() { { component: , name: 'Experience', isOptional: false }, { component: , name: 'Education', isOptional: true }, { component: , name: 'Work Experience', isOptional: true }, - { component: , name: 'Links', isOptional: true }, + { component: , name: 'Links', isOptional: true }, { component: ( diff --git a/client/src/components/Team/Modal/TeamModal.jsx b/client/src/components/Team/Modal/TeamModal.jsx index 3ccf88ce1..f423c5da7 100644 --- a/client/src/components/Team/Modal/TeamModal.jsx +++ b/client/src/components/Team/Modal/TeamModal.jsx @@ -4,8 +4,8 @@ import { Box, Modal, Slide } from '@mui/material' import Close from '../../../assets/Shared/Close' import UserPlus from '../../../assets/Team/UserPlus' import { useGetScreenWidth } from '../../../hooks/useGetScreenWidth' -import AutocompleteInput from '../../../shared/components/AutocompleteInput/AutocompleteInput' import FlexWrapper from '../../../shared/components/FlexWrapper/FlexWrapper' +import SearchUsersAutocomplete from '../../../shared/components/SearchUsersAutocomplete/SearchUsersAutocomplete' import { errorToaster } from '../../../shared/components/Toasters/Error.toaster' import { CloseContainerModal, @@ -135,7 +135,7 @@ const TeamModal = ({ 600 ? '306px' : '100%'} diff --git a/client/src/constants/countries.js b/client/src/constants/countries.js index 0c8b2ea85..f83194a3b 100644 --- a/client/src/constants/countries.js +++ b/client/src/constants/countries.js @@ -1,245 +1,424 @@ export const countries = [ - { label: 'Afghanistan', value: 'AF' }, - { label: 'Åland Islands', value: 'AX' }, - { label: 'Albania', value: 'AL' }, - { label: 'Algeria', value: 'DZ' }, - { label: 'American Samoa', value: 'AS' }, - { label: 'AndorrA', value: 'AD' }, - { label: 'Angola', value: 'AO' }, - { label: 'Anguilla', value: 'AI' }, - { label: 'Antarctica', value: 'AQ' }, - { label: 'Antigua and Barbuda', value: 'AG' }, - { label: 'Argentina', value: 'AR' }, - { label: 'Armenia', value: 'AM' }, - { label: 'Aruba', value: 'AW' }, - { label: 'Australia', value: 'AU' }, - { label: 'Austria', value: 'AT' }, - { label: 'Azerbaijan', value: 'AZ' }, - { label: 'Bahamas', value: 'BS' }, - { label: 'Bahrain', value: 'BH' }, - { label: 'Bangladesh', value: 'BD' }, - { label: 'Barbados', value: 'BB' }, - { label: 'Belarus', value: 'BY' }, - { label: 'Belgium', value: 'BE' }, - { label: 'Belize', value: 'BZ' }, - { label: 'Benin', value: 'BJ' }, - { label: 'Bermuda', value: 'BM' }, - { label: 'Bhutan', value: 'BT' }, - { label: 'Bolivia', value: 'BO' }, - { label: 'Bosnia and Herzegovina', value: 'BA' }, - { label: 'Botswana', value: 'BW' }, - { label: 'Bouvet Island', value: 'BV' }, - { label: 'Brazil', value: 'BR' }, - { label: 'British Indian Ocean Territory', value: 'IO' }, - { label: 'Brunei Darussalam', value: 'BN' }, - { label: 'Bulgaria', value: 'BG' }, - { label: 'Burkina Faso', value: 'BF' }, - { label: 'Burundi', value: 'BI' }, - { label: 'Cambodia', value: 'KH' }, - { label: 'Cameroon', value: 'CM' }, - { label: 'Canada', value: 'CA' }, - { label: 'Cape Verde', value: 'CV' }, - { label: 'Cayman Islands', value: 'KY' }, - { label: 'Central African Republic', value: 'CF' }, - { label: 'Chad', value: 'TD' }, - { label: 'Chile', value: 'CL' }, - { label: 'China', value: 'CN' }, - { label: 'Christmas Island', value: 'CX' }, - { label: 'Cocos (Keeling) Islands', value: 'CC' }, - { label: 'Colombia', value: 'CO' }, - { label: 'Comoros', value: 'KM' }, - { label: 'Congo', value: 'CG' }, - { label: 'Congo, The Democratic Republic of the', value: 'CD' }, - { label: 'Cook Islands', value: 'CK' }, - { label: 'Costa Rica', value: 'CR' }, - { label: "Cote D'Ivoire", value: 'CI' }, - { label: 'Croatia', value: 'HR' }, - { label: 'Cuba', value: 'CU' }, - { label: 'Cyprus', value: 'CY' }, - { label: 'Czech Republic', value: 'CZ' }, - { label: 'Denmark', value: 'DK' }, - { label: 'Djibouti', value: 'DJ' }, - { label: 'Dominica', value: 'DM' }, - { label: 'Dominican Republic', value: 'DO' }, - { label: 'Ecuador', value: 'EC' }, - { label: 'Egypt', value: 'EG' }, - { label: 'El Salvador', value: 'SV' }, - { label: 'Equatorial Guinea', value: 'GQ' }, - { label: 'Eritrea', value: 'ER' }, - { label: 'Estonia', value: 'EE' }, - { label: 'Ethiopia', value: 'ET' }, - { label: 'Falkland Islands (Malvinas)', value: 'FK' }, - { label: 'Faroe Islands', value: 'FO' }, - { label: 'Fiji', value: 'FJ' }, - { label: 'Finland', value: 'FI' }, - { label: 'France', value: 'FR' }, - { label: 'French Guiana', value: 'GF' }, - { label: 'French Polynesia', value: 'PF' }, - { label: 'French Southern Territories', value: 'TF' }, - { label: 'Gabon', value: 'GA' }, - { label: 'Gambia', value: 'GM' }, - { label: 'Georgia', value: 'GE' }, - { label: 'Germany', value: 'DE' }, - { label: 'Ghana', value: 'GH' }, - { label: 'Gibraltar', value: 'GI' }, - { label: 'Greece', value: 'GR' }, - { label: 'Greenland', value: 'GL' }, - { label: 'Grenada', value: 'GD' }, - { label: 'Guadeloupe', value: 'GP' }, - { label: 'Guam', value: 'GU' }, - { label: 'Guatemala', value: 'GT' }, - { label: 'Guernsey', value: 'GG' }, - { label: 'Guinea', value: 'GN' }, - { label: 'Guinea-Bissau', value: 'GW' }, - { label: 'Guyana', value: 'GY' }, - { label: 'Haiti', value: 'HT' }, - { label: 'Heard Island and Mcdonald Islands', value: 'HM' }, - { label: 'Holy See (Vatican City State)', value: 'VA' }, - { label: 'Honduras', value: 'HN' }, - { label: 'Hong Kong', value: 'HK' }, - { label: 'Hungary', value: 'HU' }, - { label: 'Iceland', value: 'IS' }, - { label: 'India', value: 'IN' }, - { label: 'Indonesia', value: 'ID' }, - { label: 'Iran, Islamic Republic Of', value: 'IR' }, - { label: 'Iraq', value: 'IQ' }, - { label: 'Ireland', value: 'IE' }, - { label: 'Isle of Man', value: 'IM' }, - { label: 'Israel', value: 'IL' }, - { label: 'Italy', value: 'IT' }, - { label: 'Jamaica', value: 'JM' }, - { label: 'Japan', value: 'JP' }, - { label: 'Jersey', value: 'JE' }, - { label: 'Jordan', value: 'JO' }, - { label: 'Kazakhstan', value: 'KZ' }, - { label: 'Kenya', value: 'KE' }, - { label: 'Kiribati', value: 'KI' }, - { label: "Korea, Democratic People'S Republic of", value: 'KP' }, - { label: 'Korea, Republic of', value: 'KR' }, - { label: 'Kuwait', value: 'KW' }, - { label: 'Kyrgyzstan', value: 'KG' }, - { label: "Lao People'S Democratic Republic", value: 'LA' }, - { label: 'Latvia', value: 'LV' }, - { label: 'Lebanon', value: 'LB' }, - { label: 'Lesotho', value: 'LS' }, - { label: 'Liberia', value: 'LR' }, - { label: 'Libyan Arab Jamahiriya', value: 'LY' }, - { label: 'Liechtenstein', value: 'LI' }, - { label: 'Lithuania', value: 'LT' }, - { label: 'Luxembourg', value: 'LU' }, - { label: 'Macao', value: 'MO' }, - { label: 'Macedonia, The Former Yugoslav Republic of', value: 'MK' }, - { label: 'Madagascar', value: 'MG' }, - { label: 'Malawi', value: 'MW' }, - { label: 'Malaysia', value: 'MY' }, - { label: 'Maldives', value: 'MV' }, - { label: 'Mali', value: 'ML' }, - { label: 'Malta', value: 'MT' }, - { label: 'Marshall Islands', value: 'MH' }, - { label: 'Martinique', value: 'MQ' }, - { label: 'Mauritania', value: 'MR' }, - { label: 'Mauritius', value: 'MU' }, - { label: 'Mayotte', value: 'YT' }, - { label: 'Mexico', value: 'MX' }, - { label: 'Micronesia, Federated States of', value: 'FM' }, - { label: 'Moldova, Republic of', value: 'MD' }, - { label: 'Monaco', value: 'MC' }, - { label: 'Mongolia', value: 'MN' }, - { label: 'Montserrat', value: 'MS' }, - { label: 'Morocco', value: 'MA' }, - { label: 'Mozambique', value: 'MZ' }, - { label: 'Myanmar', value: 'MM' }, - { label: 'Namibia', value: 'NA' }, - { label: 'Nauru', value: 'NR' }, - { label: 'Nepal', value: 'NP' }, - { label: 'Netherlands', value: 'NL' }, - { label: 'Netherlands Antilles', value: 'AN' }, - { label: 'New Caledonia', value: 'NC' }, - { label: 'New Zealand', value: 'NZ' }, - { label: 'Nicaragua', value: 'NI' }, - { label: 'Niger', value: 'NE' }, - { label: 'Nigeria', value: 'NG' }, - { label: 'Niue', value: 'NU' }, - { label: 'Norfolk Island', value: 'NF' }, - { label: 'Northern Mariana Islands', value: 'MP' }, - { label: 'Norway', value: 'NO' }, - { label: 'Oman', value: 'OM' }, - { label: 'Pakistan', value: 'PK' }, - { label: 'Palau', value: 'PW' }, - { label: 'Palestinian Territory, Occupied', value: 'PS' }, - { label: 'Panama', value: 'PA' }, - { label: 'Papua New Guinea', value: 'PG' }, - { label: 'Paraguay', value: 'PY' }, - { label: 'Peru', value: 'PE' }, - { label: 'Philippines', value: 'PH' }, - { label: 'Pitcairn', value: 'PN' }, - { label: 'Poland', value: 'PL' }, - { label: 'Portugal', value: 'PT' }, - { label: 'Puerto Rico', value: 'PR' }, - { label: 'Qatar', value: 'QA' }, - { label: 'Reunion', value: 'RE' }, - { label: 'Romania', value: 'RO' }, - { label: 'Russian Federation', value: 'RU' }, - { label: 'RWANDA', value: 'RW' }, - { label: 'Saint Helena', value: 'SH' }, - { label: 'Saint Kitts and Nevis', value: 'KN' }, - { label: 'Saint Lucia', value: 'LC' }, - { label: 'Saint Pierre and Miquelon', value: 'PM' }, - { label: 'Saint Vincent and the Grenadines', value: 'VC' }, - { label: 'Samoa', value: 'WS' }, - { label: 'San Marino', value: 'SM' }, - { label: 'Sao Tome and Principe', value: 'ST' }, - { label: 'Saudi Arabia', value: 'SA' }, - { label: 'Senegal', value: 'SN' }, - { label: 'Serbia and Montenegro', value: 'CS' }, - { label: 'Seychelles', value: 'SC' }, - { label: 'Sierra Leone', value: 'SL' }, - { label: 'Singapore', value: 'SG' }, - { label: 'Slovakia', value: 'SK' }, - { label: 'Slovenia', value: 'SI' }, - { label: 'Solomon Islands', value: 'SB' }, - { label: 'Somalia', value: 'SO' }, - { label: 'South Africa', value: 'ZA' }, - { label: 'South Georgia and the South Sandwich Islands', value: 'GS' }, - { label: 'Spain', value: 'ES' }, - { label: 'Sri Lanka', value: 'LK' }, - { label: 'Sudan', value: 'SD' }, - { label: 'Suriname', value: 'SR' }, - { label: 'Svalbard and Jan Mayen', value: 'SJ' }, - { label: 'Swaziland', value: 'SZ' }, - { label: 'Sweden', value: 'SE' }, - { label: 'Switzerland', value: 'CH' }, - { label: 'Syrian Arab Republic', value: 'SY' }, - { label: 'Taiwan, Province of China', value: 'TW' }, - { label: 'Tajikistan', value: 'TJ' }, - { label: 'Tanzania, United Republic of', value: 'TZ' }, - { label: 'Thailand', value: 'TH' }, - { label: 'Timor-Leste', value: 'TL' }, - { label: 'Togo', value: 'TG' }, - { label: 'Tokelau', value: 'TK' }, - { label: 'Tonga', value: 'TO' }, - { label: 'Trinidad and Tobago', value: 'TT' }, - { label: 'Tunisia', value: 'TN' }, - { label: 'Turkey', value: 'TR' }, - { label: 'Turkmenistan', value: 'TM' }, - { label: 'Turks and Caicos Islands', value: 'TC' }, - { label: 'Tuvalu', value: 'TV' }, - { label: 'Uganda', value: 'UG' }, - { label: 'Ukraine', value: 'UA' }, - { label: 'United Arab Emirates', value: 'AE' }, - { label: 'United Kingdom', value: 'GB' }, - { label: 'United States', value: 'US' }, - { label: 'United States Minor Outlying Islands', value: 'UM' }, - { label: 'Uruguay', value: 'UY' }, - { label: 'Uzbekistan', value: 'UZ' }, - { label: 'Vanuatu', value: 'VU' }, - { label: 'Venezuela', value: 'VE' }, - { label: 'Viet Nam', value: 'VN' }, - { label: 'Virgin Islands, British', value: 'VG' }, - { label: 'Virgin Islands, U.S.', value: 'VI' }, - { label: 'Wallis and Futuna', value: 'WF' }, - { label: 'Western Sahara', value: 'EH' }, - { label: 'Yemen', value: 'YE' }, - { label: 'Zambia', value: 'ZM' }, - { label: 'Zimbabwe', value: 'ZW' }, + { value: 'AD', label: 'Andorra', phone: '376' }, + { + value: 'AE', + label: 'United Arab Emirates', + phone: '971', + }, + { value: 'AF', label: 'Afghanistan', phone: '93' }, + { + value: 'AG', + label: 'Antigua and Barbuda', + phone: '1-268', + }, + { value: 'AI', label: 'Anguilla', phone: '1-264' }, + { value: 'AL', label: 'Albania', phone: '355' }, + { value: 'AM', label: 'Armenia', phone: '374' }, + { value: 'AO', label: 'Angola', phone: '244' }, + { value: 'AQ', label: 'Antarctica', phone: '672' }, + { value: 'AR', label: 'Argentina', phone: '54' }, + { value: 'AS', label: 'American Samoa', phone: '1-684' }, + { value: 'AT', label: 'Austria', phone: '43' }, + { + value: 'AU', + label: 'Australia', + phone: '61', + suggested: true, + }, + { value: 'AW', label: 'Aruba', phone: '297' }, + { value: 'AX', label: 'Alland Islands', phone: '358' }, + { value: 'AZ', label: 'Azerbaijan', phone: '994' }, + { + value: 'BA', + label: 'Bosnia and Herzegovina', + phone: '387', + }, + { value: 'BB', label: 'Barbados', phone: '1-246' }, + { value: 'BD', label: 'Bangladesh', phone: '880' }, + { value: 'BE', label: 'Belgium', phone: '32' }, + { value: 'BF', label: 'Burkina Faso', phone: '226' }, + { value: 'BG', label: 'Bulgaria', phone: '359' }, + { value: 'BH', label: 'Bahrain', phone: '973' }, + { value: 'BI', label: 'Burundi', phone: '257' }, + { value: 'BJ', label: 'Benin', phone: '229' }, + { value: 'BL', label: 'Saint Barthelemy', phone: '590' }, + { value: 'BM', label: 'Bermuda', phone: '1-441' }, + { value: 'BN', label: 'Brunei Darussalam', phone: '673' }, + { value: 'BO', label: 'Bolivia', phone: '591' }, + { value: 'BR', label: 'Brazil', phone: '55' }, + { value: 'BS', label: 'Bahamas', phone: '1-242' }, + { value: 'BT', label: 'Bhutan', phone: '975' }, + { value: 'BV', label: 'Bouvet Island', phone: '47' }, + { value: 'BW', label: 'Botswana', phone: '267' }, + { value: 'BY', label: 'Belarus', phone: '375' }, + { value: 'BZ', label: 'Belize', phone: '501' }, + { + value: 'CA', + label: 'Canada', + phone: '1', + suggested: true, + }, + { + value: 'CC', + label: 'Cocos (Keeling) Islands', + phone: '61', + }, + { + value: 'CD', + label: 'Congo, Democratic Republic of the', + phone: '243', + }, + { + value: 'CF', + label: 'Central African Republic', + phone: '236', + }, + { + value: 'CG', + label: 'Congo, Republic of the', + phone: '242', + }, + { value: 'CH', label: 'Switzerland', phone: '41' }, + { value: 'CI', label: "Cote d'Ivoire", phone: '225' }, + { value: 'CK', label: 'Cook Islands', phone: '682' }, + { value: 'CL', label: 'Chile', phone: '56' }, + { value: 'CM', label: 'Cameroon', phone: '237' }, + { value: 'CN', label: 'China', phone: '86' }, + { value: 'CO', label: 'Colombia', phone: '57' }, + { value: 'CR', label: 'Costa Rica', phone: '506' }, + { value: 'CU', label: 'Cuba', phone: '53' }, + { value: 'CV', label: 'Cape Verde', phone: '238' }, + { value: 'CW', label: 'Curacao', phone: '599' }, + { value: 'CX', label: 'Christmas Island', phone: '61' }, + { value: 'CY', label: 'Cyprus', phone: '357' }, + { value: 'CZ', label: 'Czech Republic', phone: '420' }, + { + value: 'DE', + label: 'Germany', + phone: '49', + suggested: true, + }, + { value: 'DJ', label: 'Djibouti', phone: '253' }, + { value: 'DK', label: 'Denmark', phone: '45' }, + { value: 'DM', label: 'Dominica', phone: '1-767' }, + { + value: 'DO', + label: 'Dominican Republic', + phone: '1-809', + }, + { value: 'DZ', label: 'Algeria', phone: '213' }, + { value: 'EC', label: 'Ecuador', phone: '593' }, + { value: 'EE', label: 'Estonia', phone: '372' }, + { value: 'EG', label: 'Egypt', phone: '20' }, + { value: 'EH', label: 'Western Sahara', phone: '212' }, + { value: 'ER', label: 'Eritrea', phone: '291' }, + { value: 'ES', label: 'Spain', phone: '34' }, + { value: 'ET', label: 'Ethiopia', phone: '251' }, + { value: 'FI', label: 'Finland', phone: '358' }, + { value: 'FJ', label: 'Fiji', phone: '679' }, + { + value: 'FK', + label: 'Falkland Islands (Malvinas)', + phone: '500', + }, + { + value: 'FM', + label: 'Micronesia, Federated States of', + phone: '691', + }, + { value: 'FO', label: 'Faroe Islands', phone: '298' }, + { + value: 'FR', + label: 'France', + phone: '33', + suggested: true, + }, + { value: 'GA', label: 'Gabon', phone: '241' }, + { value: 'GB', label: 'United Kingdom', phone: '44' }, + { value: 'GD', label: 'Grenada', phone: '1-473' }, + { value: 'GE', label: 'Georgia', phone: '995' }, + { value: 'GF', label: 'French Guiana', phone: '594' }, + { value: 'GG', label: 'Guernsey', phone: '44' }, + { value: 'GH', label: 'Ghana', phone: '233' }, + { value: 'GI', label: 'Gibraltar', phone: '350' }, + { value: 'GL', label: 'Greenland', phone: '299' }, + { value: 'GM', label: 'Gambia', phone: '220' }, + { value: 'GN', label: 'Guinea', phone: '224' }, + { value: 'GP', label: 'Guadeloupe', phone: '590' }, + { value: 'GQ', label: 'Equatorial Guinea', phone: '240' }, + { value: 'GR', label: 'Greece', phone: '30' }, + { + value: 'GS', + label: 'South Georgia and the South Sandwich Islands', + phone: '500', + }, + { value: 'GT', label: 'Guatemala', phone: '502' }, + { value: 'GU', label: 'Guam', phone: '1-671' }, + { value: 'GW', label: 'Guinea-Bissau', phone: '245' }, + { value: 'GY', label: 'Guyana', phone: '592' }, + { value: 'HK', label: 'Hong Kong', phone: '852' }, + { + value: 'HM', + label: 'Heard Island and McDonald Islands', + phone: '672', + }, + { value: 'HN', label: 'Honduras', phone: '504' }, + { value: 'HR', label: 'Croatia', phone: '385' }, + { value: 'HT', label: 'Haiti', phone: '509' }, + { value: 'HU', label: 'Hungary', phone: '36' }, + { value: 'ID', label: 'Indonesia', phone: '62' }, + { value: 'IE', label: 'Ireland', phone: '353' }, + { value: 'IL', label: 'Israel', phone: '972' }, + { value: 'IM', label: 'Isle of Man', phone: '44' }, + { value: 'IN', label: 'India', phone: '91' }, + { + value: 'IO', + label: 'British Indian Ocean Territory', + phone: '246', + }, + { value: 'IQ', label: 'Iraq', phone: '964' }, + { + value: 'IR', + label: 'Iran, Islamic Republic of', + phone: '98', + }, + { value: 'IS', label: 'Iceland', phone: '354' }, + { value: 'IT', label: 'Italy', phone: '39' }, + { value: 'JE', label: 'Jersey', phone: '44' }, + { value: 'JM', label: 'Jamaica', phone: '1-876' }, + { value: 'JO', label: 'Jordan', phone: '962' }, + { + value: 'JP', + label: 'Japan', + phone: '81', + suggested: true, + }, + { value: 'KE', label: 'Kenya', phone: '254' }, + { value: 'KG', label: 'Kyrgyzstan', phone: '996' }, + { value: 'KH', label: 'Cambodia', phone: '855' }, + { value: 'KI', label: 'Kiribati', phone: '686' }, + { value: 'KM', label: 'Comoros', phone: '269' }, + { + value: 'KN', + label: 'Saint Kitts and Nevis', + phone: '1-869', + }, + { + value: 'KP', + label: "Korea, Democratic People's Republic of", + phone: '850', + }, + { value: 'KR', label: 'Korea, Republic of', phone: '82' }, + { value: 'KW', label: 'Kuwait', phone: '965' }, + { value: 'KY', label: 'Cayman Islands', phone: '1-345' }, + { value: 'KZ', label: 'Kazakhstan', phone: '7' }, + { + value: 'LA', + label: "Lao People's Democratic Republic", + phone: '856', + }, + { value: 'LB', label: 'Lebanon', phone: '961' }, + { value: 'LC', label: 'Saint Lucia', phone: '1-758' }, + { value: 'LI', label: 'Liechtenstein', phone: '423' }, + { value: 'LK', label: 'Sri Lanka', phone: '94' }, + { value: 'LR', label: 'Liberia', phone: '231' }, + { value: 'LS', label: 'Lesotho', phone: '266' }, + { value: 'LT', label: 'Lithuania', phone: '370' }, + { value: 'LU', label: 'Luxembourg', phone: '352' }, + { value: 'LV', label: 'Latvia', phone: '371' }, + { value: 'LY', label: 'Libya', phone: '218' }, + { value: 'MA', label: 'Morocco', phone: '212' }, + { value: 'MC', label: 'Monaco', phone: '377' }, + { + value: 'MD', + label: 'Moldova, Republic of', + phone: '373', + }, + { value: 'ME', label: 'Montenegro', phone: '382' }, + { + value: 'MF', + label: 'Saint Martin (French part)', + phone: '590', + }, + { value: 'MG', label: 'Madagascar', phone: '261' }, + { value: 'MH', label: 'Marshall Islands', phone: '692' }, + { + value: 'MK', + label: 'Macedonia, the Former Yugoslav Republic of', + phone: '389', + }, + { value: 'ML', label: 'Mali', phone: '223' }, + { value: 'MM', label: 'Myanmar', phone: '95' }, + { value: 'MN', label: 'Mongolia', phone: '976' }, + { value: 'MO', label: 'Macao', phone: '853' }, + { + value: 'MP', + label: 'Northern Mariana Islands', + phone: '1-670', + }, + { value: 'MQ', label: 'Martinique', phone: '596' }, + { value: 'MR', label: 'Mauritania', phone: '222' }, + { value: 'MS', label: 'Montserrat', phone: '1-664' }, + { value: 'MT', label: 'Malta', phone: '356' }, + { value: 'MU', label: 'Mauritius', phone: '230' }, + { value: 'MV', label: 'Maldives', phone: '960' }, + { value: 'MW', label: 'Malawi', phone: '265' }, + { value: 'MX', label: 'Mexico', phone: '52' }, + { value: 'MY', label: 'Malaysia', phone: '60' }, + { value: 'MZ', label: 'Mozambique', phone: '258' }, + { value: 'NA', label: 'Namibia', phone: '264' }, + { value: 'NC', label: 'New Caledonia', phone: '687' }, + { value: 'NE', label: 'Niger', phone: '227' }, + { value: 'NF', label: 'Norfolk Island', phone: '672' }, + { value: 'NG', label: 'Nigeria', phone: '234' }, + { value: 'NI', label: 'Nicaragua', phone: '505' }, + { value: 'NL', label: 'Netherlands', phone: '31' }, + { value: 'NO', label: 'Norway', phone: '47' }, + { value: 'NP', label: 'Nepal', phone: '977' }, + { value: 'NR', label: 'Nauru', phone: '674' }, + { value: 'NU', label: 'Niue', phone: '683' }, + { value: 'NZ', label: 'New Zealand', phone: '64' }, + { value: 'OM', label: 'Oman', phone: '968' }, + { value: 'PA', label: 'Panama', phone: '507' }, + { value: 'PE', label: 'Peru', phone: '51' }, + { value: 'PF', label: 'French Polynesia', phone: '689' }, + { value: 'PG', label: 'Papua New Guinea', phone: '675' }, + { value: 'PH', label: 'Philippines', phone: '63' }, + { value: 'PK', label: 'Pakistan', phone: '92' }, + { value: 'PL', label: 'Poland', phone: '48' }, + { + value: 'PM', + label: 'Saint Pierre and Miquelon', + phone: '508', + }, + { value: 'PN', label: 'Pitcairn', phone: '870' }, + { value: 'PR', label: 'Puerto Rico', phone: '1' }, + { + value: 'PS', + label: 'Palestine, State of', + phone: '970', + }, + { value: 'PT', label: 'Portugal', phone: '351' }, + { value: 'PW', label: 'Palau', phone: '680' }, + { value: 'PY', label: 'Paraguay', phone: '595' }, + { value: 'QA', label: 'Qatar', phone: '974' }, + { value: 'RE', label: 'Reunion', phone: '262' }, + { value: 'RO', label: 'Romania', phone: '40' }, + { value: 'RS', label: 'Serbia', phone: '381' }, + { value: 'RU', label: 'Russian Federation', phone: '7' }, + { value: 'RW', label: 'Rwanda', phone: '250' }, + { value: 'SA', label: 'Saudi Arabia', phone: '966' }, + { value: 'SB', label: 'Solomon Islands', phone: '677' }, + { value: 'SC', label: 'Seychelles', phone: '248' }, + { value: 'SD', label: 'Sudan', phone: '249' }, + { value: 'SE', label: 'Sweden', phone: '46' }, + { value: 'SG', label: 'Singapore', phone: '65' }, + { value: 'SH', label: 'Saint Helena', phone: '290' }, + { value: 'SI', label: 'Slovenia', phone: '386' }, + { + value: 'SJ', + label: 'Svalbard and Jan Mayen', + phone: '47', + }, + { value: 'SK', label: 'Slovakia', phone: '421' }, + { value: 'SL', label: 'Sierra Leone', phone: '232' }, + { value: 'SM', label: 'San Marino', phone: '378' }, + { value: 'SN', label: 'Senegal', phone: '221' }, + { value: 'SO', label: 'Somalia', phone: '252' }, + { value: 'SR', label: 'Suriname', phone: '597' }, + { value: 'SS', label: 'South Sudan', phone: '211' }, + { + value: 'ST', + label: 'Sao Tome and Principe', + phone: '239', + }, + { value: 'SV', label: 'El Salvador', phone: '503' }, + { + value: 'SX', + label: 'Sint Maarten (Dutch part)', + phone: '1-721', + }, + { + value: 'SY', + label: 'Syrian Arab Republic', + phone: '963', + }, + { value: 'SZ', label: 'Swaziland', phone: '268' }, + { + value: 'TC', + label: 'Turks and Caicos Islands', + phone: '1-649', + }, + { value: 'TD', label: 'Chad', phone: '235' }, + { + value: 'TF', + label: 'French Southern Territories', + phone: '262', + }, + { value: 'TG', label: 'Togo', phone: '228' }, + { value: 'TH', label: 'Thailand', phone: '66' }, + { value: 'TJ', label: 'Tajikistan', phone: '992' }, + { value: 'TK', label: 'Tokelau', phone: '690' }, + { value: 'TL', label: 'Timor-Leste', phone: '670' }, + { value: 'TM', label: 'Turkmenistan', phone: '993' }, + { value: 'TN', label: 'Tunisia', phone: '216' }, + { value: 'TO', label: 'Tonga', phone: '676' }, + { value: 'TR', label: 'Turkey', phone: '90' }, + { + value: 'TT', + label: 'Trinidad and Tobago', + phone: '1-868', + }, + { value: 'TV', label: 'Tuvalu', phone: '688' }, + { + value: 'TW', + label: 'Taiwan, Republic of China', + phone: '886', + }, + { + value: 'TZ', + label: 'United Republic of Tanzania', + phone: '255', + }, + { value: 'UA', label: 'Ukraine', phone: '380' }, + { value: 'UG', label: 'Uganda', phone: '256' }, + { + value: 'US', + label: 'United States', + phone: '1', + suggested: true, + }, + { value: 'UY', label: 'Uruguay', phone: '598' }, + { value: 'UZ', label: 'Uzbekistan', phone: '998' }, + { + value: 'VA', + label: 'Holy See (Vatican City State)', + phone: '379', + }, + { + value: 'VC', + label: 'Saint Vincent and the Grenadines', + phone: '1-784', + }, + { value: 'VE', label: 'Venezuela', phone: '58' }, + { + value: 'VG', + label: 'British Virgin Islands', + phone: '1-284', + }, + { + value: 'VI', + label: 'US Virgin Islands', + phone: '1-340', + }, + { value: 'VN', label: 'Vietnam', phone: '84' }, + { value: 'VU', label: 'Vanuatu', phone: '678' }, + { value: 'WF', label: 'Wallis and Futuna', phone: '681' }, + { value: 'WS', label: 'Samoa', phone: '685' }, + { value: 'XK', label: 'Kosovo', phone: '383' }, + { value: 'YE', label: 'Yemen', phone: '967' }, + { value: 'YT', label: 'Mayotte', phone: '262' }, + { value: 'ZA', label: 'South Africa', phone: '27' }, + { value: 'ZM', label: 'Zambia', phone: '260' }, + { value: 'ZW', label: 'Zimbabwe', phone: '263' }, ] diff --git a/client/src/constants/finishRegistrationData.js b/client/src/constants/finishRegistrationData.js index db81cf27e..b593dd5ac 100644 --- a/client/src/constants/finishRegistrationData.js +++ b/client/src/constants/finishRegistrationData.js @@ -69,19 +69,19 @@ export const defaultUserAvatars = [ export const userExperienceOptions = [ { - label: 'No experience', + label: '0', value: '0', }, { - label: '1-3 years', + label: '1-3', value: '1-3', }, { - label: '3-5 years', + label: '3-5', value: '3-5', }, { - label: '5+ years', + label: '5+', value: '5+', }, ] diff --git a/client/src/shared/components/Formik/CustomInput/CustomInput.styles.js b/client/src/shared/components/Formik/CustomInput/CustomInput.styles.js index 70b79ca47..cb82fccfc 100644 --- a/client/src/shared/components/Formik/CustomInput/CustomInput.styles.js +++ b/client/src/shared/components/Formik/CustomInput/CustomInput.styles.js @@ -26,6 +26,7 @@ export const Input = styled.input` animation-name: ${(props) => props.animation || shake}; animation-duration: 0.3s; display: ${(props) => (props.isOptional ? 'none' : 'block')}; + padding: 8px 4px; &:focus { border-color: ${WHITE.main}; diff --git a/client/src/shared/components/Formik/CustomSelect/CustomSelect.styles.js b/client/src/shared/components/Formik/CustomSelect/CustomSelect.styles.js index 878236c68..e6b3e0cdf 100644 --- a/client/src/shared/components/Formik/CustomSelect/CustomSelect.styles.js +++ b/client/src/shared/components/Formik/CustomSelect/CustomSelect.styles.js @@ -1,3 +1,4 @@ +import { Autocomplete } from '@mui/material' import MenuItem from '@mui/material/MenuItem' import Select from '@mui/material/Select' import styled from 'styled-components' @@ -5,8 +6,8 @@ import styled from 'styled-components' import { GREEN, GREY, WHITE } from '../../../../constants/colors' import { shake } from '../../../styles/KeyFrames.styles' -export const SelectCustom = styled(Select)` - animation-name: ${(props) => (!props.$isError ? 'none' : shake)}; +export const SelectCustom = styled(Autocomplete)` + /* animation-name: ${(props) => (!props.$isError ? 'none' : shake)}; animation-duration: 0.3s; background: none; height: 45px; @@ -27,7 +28,7 @@ export const SelectCustom = styled(Select)` & .css-11u53oe-MuiSelect-select-MuiInputBase-input-MuiOutlinedInput-input { padding: 0; color: ${WHITE.main}; - } + } */ ` export const Line = styled.hr` diff --git a/client/src/shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete.jsx b/client/src/shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete.jsx new file mode 100644 index 000000000..f903dd5aa --- /dev/null +++ b/client/src/shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete.jsx @@ -0,0 +1,64 @@ +import { Autocomplete, FormControl, TextField, ThemeProvider } from '@mui/material' +import { useField, useFormikContext } from 'formik' + +import ArrowDown from '../../../../assets/Arrows/ArrowDown' +import { Label } from '../../../styles/Tpography.styles' + +import { theme } from './CustomSelectAutocomplete.theme' + +const CustomSelectAutocomplete = ({ + label, + options, + multiple = false, + width, + placeholder, + displayError = true, + margin, + hideLabelOnSelect = false, + ...props +}) => { + const { setFieldValue } = useFormikContext() + const [field, meta] = useField(props) + const isError = meta.touched && meta.error + + return ( + + + {!hideLabelOnSelect && label && } + + } + autoHighlight + isOptionEqualToValue={(option, value) => option.label === value} + multiple={multiple} + onChange={(e, option) => setFieldValue(field.name, option.label)} + renderInput={(params) => ( + + )} + /> + + {/* {line && } + {displayError && isError && {meta.error}} */} + + + ) +} + +export default CustomSelectAutocomplete diff --git a/client/src/shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete.theme.js b/client/src/shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete.theme.js new file mode 100644 index 000000000..46a6a4ca8 --- /dev/null +++ b/client/src/shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete.theme.js @@ -0,0 +1,76 @@ +import { createTheme } from '@mui/material' + +export const theme = createTheme({ + components: { + // Name of the component + MuiAutocomplete: { + styleOverrides: { + // Name of the slot + noOptions: { + color: 'white', + fontSize: 16, + fontWeight: 400, + }, + root: { + width: '100%', + cursor: 'pointer', + ':hover': { + background: '#2F3239', + }, + }, + listbox: { + maxHeight: '250px', + color: 'white', + '.MuiAutocomplete-option': { + '&.Mui-focused': { + backgroundColor: '#27431F', + }, + '&[aria-selected="true"]': { + backgroundColor: 'transparent', + }, + '&.Mui-focused[aria-selected="true"]': { + backgroundColor: '#27431F', + }, + }, + + '::-webkit-scrollbar': { + /* WebKit */ + transition: 'all 0.2s', + width: '5px', + height: 'auto', + }, + + '::-webkit-scrollbar-track': { + background: 'transparent', + }, + + '::-webkit-scrollbar-thumb': { + background: `#5D9D0B`, + borderRadius: '10px', + }, + }, + paper: { + background: '#2F3239', + padding: '8px 0', + margin: '10px 0 0 0', + }, + inputRoot: { + padding: '0 !important', + }, + input: { + padding: '8px 4px', + cursor: 'pointer', + '::placeholder': { + fontWeight: '400', + }, + }, + clearIndicator: { + color: 'white', + }, + endAdornment: { + right: '0 !important', + }, + }, + }, + }, +}) diff --git a/client/src/components/RegistrationPipeline/components/RegistrationForms/UserLinksForm/UserLinksForm.jsx b/client/src/shared/components/Links/Links.jsx similarity index 52% rename from client/src/components/RegistrationPipeline/components/RegistrationForms/UserLinksForm/UserLinksForm.jsx rename to client/src/shared/components/Links/Links.jsx index 0fb370e84..68286d4b2 100644 --- a/client/src/components/RegistrationPipeline/components/RegistrationForms/UserLinksForm/UserLinksForm.jsx +++ b/client/src/shared/components/Links/Links.jsx @@ -1,11 +1,11 @@ import React from 'react' -import { userLinks } from '../../../../../constants/finishRegistrationData' -import CustomInput from '../../../../../shared/components/Formik/CustomInput/CustomInput' -import { InputWithIConWrapper } from '../../../../../shared/components/Formik/CustomInput/CustomInput.styles' -import { ContentContainer } from '../../MultiStepRegistration/MultiStepRegistration.styles' +import { ContentContainer } from '../../../components/RegistrationPipeline/components/MultiStepRegistration/MultiStepRegistration.styles' +import { userLinks } from '../../../constants/finishRegistrationData' +import CustomInput from '../Formik/CustomInput/CustomInput' +import { InputWithIConWrapper } from '../Formik/CustomInput/CustomInput.styles' -const UserLinksForm = () => { +const Links = () => { return ( <> @@ -25,4 +25,4 @@ const UserLinksForm = () => { ) } -export default UserLinksForm +export default Links diff --git a/client/src/shared/components/Links/Links.styles.jsx b/client/src/shared/components/Links/Links.styles.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/shared/components/AutocompleteInput/List.js b/client/src/shared/components/SearchUsersAutocomplete/List.js similarity index 100% rename from client/src/shared/components/AutocompleteInput/List.js rename to client/src/shared/components/SearchUsersAutocomplete/List.js diff --git a/client/src/shared/components/AutocompleteInput/Loader.js b/client/src/shared/components/SearchUsersAutocomplete/Loader.js similarity index 81% rename from client/src/shared/components/AutocompleteInput/Loader.js rename to client/src/shared/components/SearchUsersAutocomplete/Loader.js index 9b6bf50d5..b719b1051 100644 --- a/client/src/shared/components/AutocompleteInput/Loader.js +++ b/client/src/shared/components/SearchUsersAutocomplete/Loader.js @@ -1,6 +1,6 @@ import { CircularProgress } from '@mui/material' -import { LoaderContainer, LoaderText } from './AutocompleteInput.styles' +import { LoaderContainer, LoaderText } from './SearchUsersAutocomplete.styles' export const InputLoader = ({ areUsersLoading }) => { if (areUsersLoading) { diff --git a/client/src/shared/components/AutocompleteInput/AutocompleteInput.jsx b/client/src/shared/components/SearchUsersAutocomplete/SearchUsersAutocomplete.jsx similarity index 93% rename from client/src/shared/components/AutocompleteInput/AutocompleteInput.jsx rename to client/src/shared/components/SearchUsersAutocomplete/SearchUsersAutocomplete.jsx index 61f418106..c8de05649 100644 --- a/client/src/shared/components/AutocompleteInput/AutocompleteInput.jsx +++ b/client/src/shared/components/SearchUsersAutocomplete/SearchUsersAutocomplete.jsx @@ -2,16 +2,15 @@ import { useEffect, useMemo, useState } from 'react' import { ThemeProvider } from '@mui/material' import Autocomplete from '@mui/material/Autocomplete' import { debounce } from '@mui/material/utils' -import { useField } from 'formik' import { useGetUserByUsername } from '../../../api/hooks/temeights/useGetUserByUsername' -import { theme } from './AutocompleteInput.theme' import { List } from './List' import { InputLoader } from './Loader' +import { theme } from './SearchUsersAutocomplete.theme' import { InputTextField } from './TextField' -export default function AutocompleteInput({ value, setValue, width = 412, ...props }) { +export default function SearchUsersAutocomplete({ value, setValue, width = 412, ...props }) { const [inputValue, setInputValue] = useState('') const [user, setUser] = useState([]) diff --git a/client/src/shared/components/AutocompleteInput/AutocompleteInput.styles.js b/client/src/shared/components/SearchUsersAutocomplete/SearchUsersAutocomplete.styles.js similarity index 100% rename from client/src/shared/components/AutocompleteInput/AutocompleteInput.styles.js rename to client/src/shared/components/SearchUsersAutocomplete/SearchUsersAutocomplete.styles.js diff --git a/client/src/shared/components/AutocompleteInput/AutocompleteInput.theme.js b/client/src/shared/components/SearchUsersAutocomplete/SearchUsersAutocomplete.theme.js similarity index 100% rename from client/src/shared/components/AutocompleteInput/AutocompleteInput.theme.js rename to client/src/shared/components/SearchUsersAutocomplete/SearchUsersAutocomplete.theme.js diff --git a/client/src/shared/components/AutocompleteInput/TextField.js b/client/src/shared/components/SearchUsersAutocomplete/TextField.js similarity index 100% rename from client/src/shared/components/AutocompleteInput/TextField.js rename to client/src/shared/components/SearchUsersAutocomplete/TextField.js From 5d701276479011382737554c234dfe421e075b52 Mon Sep 17 00:00:00 2001 From: Nikita Mashchenko Date: Fri, 30 Jun 2023 06:10:52 -0500 Subject: [PATCH 08/11] Improvements & fixes --- client/src/assets/UserProfile/CrossIcon.js | 15 ++ .../src/assets/UserProfile/PlusIconWhite.jsx | 20 ++ client/src/components/Profile/Profile.jsx | 65 +++-- .../UserStatusButtons/UserStatusButtons.jsx | 2 +- .../EditingComponentDefault.jsx | 10 +- .../EditingComponentDescription.jsx | 35 +++ .../EditingComponentFrameworks.jsx | 35 +++ .../EditingComponentLanguages.jsx | 35 +++ .../EditingComponentProjects.jsx | 231 ++++++++++++++++++ .../ProjectsSkills/ProjectsSkills.jsx | 72 +++--- .../components/ResumeInfo/ResumeInfo.jsx | 30 ++- .../ResumeInfo/ResumeInfo.styles.js | 36 ++- .../components/ResumeInfo/TagLink/TagLink.jsx | 23 +- .../ResumeInfo/TagLink/TagLink.styles.js | 20 +- .../src/components/Team/Members/Members.jsx | 2 +- .../src/components/Team/TeamForm/TeamForm.js | 4 +- .../Team/TeamForm/TeamForm.styles.js | 64 ----- .../components/Team/TeamsList/TeamsList.js | 5 +- client/src/schemas/index.js | 27 +- .../CustomSelectAutocomplete.jsx | 58 ++++- .../CustomSelectAutocomplete.styles.js | 3 + .../CustomSelectAutocomplete.theme.js | 30 ++- .../Formik/CustomTextArea/CustomTextArea.jsx | 8 +- .../CustomTextArea/CustomTextArea.styles.js | 7 +- .../Modal/LeaderOptions/LeaderOptions.jsx | 4 +- .../components/Modal/Modal.jsx} | 42 ++-- .../components/Modal/Modal.styles.jsx} | 80 ++++++ .../Modal/ModalTypes/ActionModal.jsx | 3 +- .../Modal/ModalTypes/InfoModal.jsx | 3 +- .../Modal/ModalTypes/InteractiveModal.jsx | 3 +- .../TeamPreviewModal/TeamPreviewModal.jsx | 0 .../TeamPreviewModal.styles.js | 0 .../TeamPreviewModalPhone.jsx | 0 .../TeamPreviewModalPhone.styles.jsx | 0 client/src/utils/filterArrayByDeepEquality.js | 5 + server/src/pipes/validation.pipe.ts | 31 ++- server/src/users/dto/project-data.dto.ts | 23 ++ server/src/users/dto/update-user.dto.ts | 16 ++ server/src/users/users.controller.ts | 1 + server/src/users/users.schema.ts | 26 ++ .../constants/allowed-update-user-fields.ts | 1 + 41 files changed, 878 insertions(+), 197 deletions(-) create mode 100644 client/src/assets/UserProfile/CrossIcon.js create mode 100644 client/src/assets/UserProfile/PlusIconWhite.jsx create mode 100644 client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDescription.jsx create mode 100644 client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentFrameworks.jsx create mode 100644 client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentLanguages.jsx create mode 100644 client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProjects.jsx create mode 100644 client/src/shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete.styles.js rename client/src/{components/Team => shared/components}/Modal/LeaderOptions/LeaderOptions.jsx (93%) rename client/src/{components/Team/Modal/TeamModal.jsx => shared/components/Modal/Modal.jsx} (92%) rename client/src/{components/Team/Modal/TeamModal.styles.jsx => shared/components/Modal/Modal.styles.jsx} (58%) rename client/src/{components/Team => shared/components}/Modal/ModalTypes/ActionModal.jsx (90%) rename client/src/{components/Team => shared/components}/Modal/ModalTypes/InfoModal.jsx (87%) rename client/src/{components/Team => shared/components}/Modal/ModalTypes/InteractiveModal.jsx (86%) rename client/src/{components/Team => shared/components}/Modal/TeamPreviewModal/TeamPreviewModal.jsx (100%) rename client/src/{components/Team => shared/components}/Modal/TeamPreviewModal/TeamPreviewModal.styles.js (100%) rename client/src/{components/Team => shared/components}/Modal/TeamPreviewModalPhone/TeamPreviewModalPhone.jsx (100%) rename client/src/{components/Team => shared/components}/Modal/TeamPreviewModalPhone/TeamPreviewModalPhone.styles.jsx (100%) create mode 100644 client/src/utils/filterArrayByDeepEquality.js create mode 100644 server/src/users/dto/project-data.dto.ts diff --git a/client/src/assets/UserProfile/CrossIcon.js b/client/src/assets/UserProfile/CrossIcon.js new file mode 100644 index 000000000..73ef9ceb2 --- /dev/null +++ b/client/src/assets/UserProfile/CrossIcon.js @@ -0,0 +1,15 @@ +function CrossIcon() { + return ( + + + + ) +} + +export default CrossIcon diff --git a/client/src/assets/UserProfile/PlusIconWhite.jsx b/client/src/assets/UserProfile/PlusIconWhite.jsx new file mode 100644 index 000000000..b7933d88a --- /dev/null +++ b/client/src/assets/UserProfile/PlusIconWhite.jsx @@ -0,0 +1,20 @@ +const PlusIconWhite = () => { + return ( + + + + + ) +} + +export default PlusIconWhite diff --git a/client/src/components/Profile/Profile.jsx b/client/src/components/Profile/Profile.jsx index e7c77827d..664a6e075 100644 --- a/client/src/components/Profile/Profile.jsx +++ b/client/src/components/Profile/Profile.jsx @@ -10,6 +10,7 @@ import PlatformLogo from '../../assets/Platform/TeameightsLogo' import { usePrompt } from '../../hooks/usePrompt' import { editProfileValidation } from '../../schemas' import Loader from '../../shared/components/Loader/Loader' +import ModalComponent from '../../shared/components/Modal/Modal' import Page404Form from '../Forms/Page404Form/Page404Form' import ProfileInfo from './components/ProfileInfo/ProfileInfo' @@ -43,6 +44,8 @@ const Profile = () => { linkedIn, programmingLanguages, frameworks, + dateOfBirth, + projectData, file, } = values const modifiedUserData = { @@ -59,17 +62,28 @@ const Profile = () => { }, programmingLanguages, frameworks, + dateOfBirth, + projectData, } - editUserDetails(modifiedUserData) - if (file) { updateAvatar({ email: showingUser?.email, image: file.split(',')[1] }) + } else { + editUserDetails(modifiedUserData) } setIsEditing('') + + // setTimeout(function () { + // editUserDetails(modifiedUserData) + // setIsEditing('') + // }, 2000) } + // const handleClose = () => { + // setModalActive('') + // } + if (isLoading || isUserLoading || isFetching || isUpdatingAvatar || !showingUser) { return } @@ -94,6 +108,7 @@ const Profile = () => { programmingLanguages: showingUser?.programmingLanguages, frameworks: showingUser?.frameworks, dateOfBirth: showingUser?.dateOfBirth, + projectData: showingUser?.projectData, file: null, }} validationSchema={editProfileValidation} @@ -104,27 +119,31 @@ const Profile = () => { usePrompt('You have unsaved changes. Do you want to discard them?', dirty) return ( - - - - - - - - - - - + <> + + + + + + + + + + + + ) }} diff --git a/client/src/components/Profile/components/ProfileInfo/UserStatusButtons/UserStatusButtons.jsx b/client/src/components/Profile/components/ProfileInfo/UserStatusButtons/UserStatusButtons.jsx index 8d3780ee1..ad4952edf 100644 --- a/client/src/components/Profile/components/ProfileInfo/UserStatusButtons/UserStatusButtons.jsx +++ b/client/src/components/Profile/components/ProfileInfo/UserStatusButtons/UserStatusButtons.jsx @@ -19,7 +19,7 @@ const UserStatusButtons = ({ userStatus, }) => { const renderSameUserButtons = () => { - if (isEditing && isEditing !== 'avatar') { + if (isEditing && isEditing === 'profile') { return ( diff --git a/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDefault.jsx b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDefault.jsx index 586374ddb..633c004fb 100644 --- a/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDefault.jsx +++ b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDefault.jsx @@ -3,7 +3,7 @@ import EducationWork from '../EducationWork/EducationWork' import ProjectsSkills from '../ProjectsSkills/ProjectsSkills' import { ResumePartBox, ResumePartBtn } from '../ResumeInfo.styles' -function EditingComponentDefault({ active, setActive, showingUser }) { +function EditingComponentDefault({ active, setActive, showingUser, setIsEditing, userStatus }) { return ( @@ -14,7 +14,13 @@ function EditingComponentDefault({ active, setActive, showingUser }) { Education & Work - {active === 'projects' && } + {active === 'projects' && ( + + )} {active === 'education' && } ) diff --git a/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDescription.jsx b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDescription.jsx new file mode 100644 index 000000000..39875ed7b --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentDescription.jsx @@ -0,0 +1,35 @@ +import { useFormikContext } from 'formik' + +import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' +import CustomTextArea from '../../../../../shared/components/Formik/CustomTextArea/CustomTextArea' +import { ActionButton, Text, TextArea } from '../ResumeInfo.styles' + +function EditingComponentDescription({ handleCancel }) { + const { values } = useFormikContext() + + return ( + + + About me + + + + + Cancel + + + Save + + + + ) +} + +export default EditingComponentDescription diff --git a/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentFrameworks.jsx b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentFrameworks.jsx new file mode 100644 index 000000000..b867ae15c --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentFrameworks.jsx @@ -0,0 +1,35 @@ +import { useFormikContext } from 'formik' + +import frameworkOptions from '../../../../../constants/frameworks' +import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' +import CustomSelectAutocomplete from '../../../../../shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete' +import { ActionButton, Text } from '../ResumeInfo.styles' + +function EditingComponentFrameworks({ handleCancel }) { + const { values } = useFormikContext() + + return ( + + + Frameworks + + + + + Cancel + + + Save + + + + ) +} + +export default EditingComponentFrameworks diff --git a/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentLanguages.jsx b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentLanguages.jsx new file mode 100644 index 000000000..44b03a618 --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentLanguages.jsx @@ -0,0 +1,35 @@ +import { useFormikContext } from 'formik' + +import { programmingLanguageOptions } from '../../../../../constants/programmingLanguages' +import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' +import CustomSelectAutocomplete from '../../../../../shared/components/Formik/CustomSelectAutocomplete/CustomSelectAutocomplete' +import { ActionButton, Text } from '../ResumeInfo.styles' + +function EditingComponentLanguages({ handleCancel }) { + const { values } = useFormikContext() + + return ( + + + Languages + + + + + Cancel + + + Save + + + + ) +} + +export default EditingComponentLanguages diff --git a/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProjects.jsx b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProjects.jsx new file mode 100644 index 000000000..444b3fc2f --- /dev/null +++ b/client/src/components/Profile/components/ResumeInfo/EditingComponents/EditingComponentProjects.jsx @@ -0,0 +1,231 @@ +import { useState } from 'react' +import { ThreeDots } from 'react-loader-spinner' +import { FieldArray, useFormikContext } from 'formik' + +import { useEditUserDetails } from '../../../../../api/hooks/user/useEditUserDetails' +import LongArrowLeft from '../../../../../assets/Arrows/LongArrowLeft' +import CrossIcon from '../../../../../assets/UserProfile/CrossIcon' +import EditIcon from '../../../../../assets/UserProfile/EditIcon' +import PlusIconWhite from '../../../../../assets/UserProfile/PlusIconWhite' +import FlexWrapper from '../../../../../shared/components/FlexWrapper/FlexWrapper' +import CustomInput from '../../../../../shared/components/Formik/CustomInput/CustomInput' +import ModalComponent from '../../../../../shared/components/Modal/Modal' +import { ActionButton, EditIconContainer, Text } from '../ResumeInfo.styles' + +function EditingComponentProjects({ handleBack, showingUser }) { + const [modalActive, setModalActive] = useState('') + const { values, setFieldValue, resetForm } = useFormikContext() + const { mutate: editUserDetails, isLoading } = useEditUserDetails() + const [currentAction, setCurrentAction] = useState('main') // 'main', 'editing', 'adding' + const [index, setIndex] = useState(0) + + const handleRemoveProject = () => { + const projects = [...values.projectData] + + const updatedProjects = projects.filter((_, i) => i !== index) + + setFieldValue('projectData', updatedProjects) + + editUserDetails({ email: showingUser.email, projectData: updatedProjects }) + + setModalActive('') + } + + const handleEditProject = (index) => { + setIndex(index) + setCurrentAction('editing') + } + + const submitEditProject = () => { + editUserDetails({ email: showingUser.email, projectData: values.projectData }) + setModalActive('') + } + + const handleOpenModal = (index) => { + setModalActive('DeleteProject') + setIndex(index) + } + + const handleClose = () => { + setModalActive('') + } + + const handleCancel = () => { + setCurrentAction('main') + resetForm() + } + + return ( + <> + + + + Projects + + {currentAction === 'main' && ( + + {({ push, remove }) => ( + <> + {values?.projectData?.length > 0 ? ( + values?.projectData.map((project, index) => ( + + + + {project.title} + + + {project.link} + + + + handleEditProject(index)} + > + + + handleOpenModal(index)} + > + + + + + )) + ) : ( + + No projects added yet. + + )} + + + + Back + + { + push({ title: '', link: '' }) + setCurrentAction('adding') + }} + type="button" + > + Add new + + + + + )} + + )} + + {currentAction === 'editing' && ( + <> + + + + + + Cancel + + + {isLoading ? ( + + ) : ( + 'Save' + )} + + + + + )} + + {currentAction === 'adding' && ( + <> + + + + + + Cancel + + + {isLoading ? ( + + ) : ( + 'Save' + )} + + + + + )} + + + ) +} + +export default EditingComponentProjects diff --git a/client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx b/client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx index 5eff87a4f..c548c5f31 100644 --- a/client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx +++ b/client/src/components/Profile/components/ResumeInfo/ProjectsSkills/ProjectsSkills.jsx @@ -1,3 +1,5 @@ +import { useFormikContext } from 'formik' + import EditIcon from '../../../../../assets/UserProfile/EditIcon' import LinkIcon from '../../../../../assets/UserProfile/LinkIcon' import TeamMembersIcon from '../../../../../assets/UserProfile/TeamMembersIcon' @@ -14,15 +16,7 @@ import { } from '../ResumeInfo.styles' import TagLink from '../TagLink/TagLink' -const ProjectsSkills = ({ showingUser }) => { - const projectsArr = [ - { text: 'Team8s', link: '#' }, - { text: 'BankingApp', link: '#' }, - { text: 'Snake', link: '#' }, - { text: 'Shopping App', link: '#' }, - { text: 'E-learning', link: '#' }, - ] - +const ProjectsSkills = ({ showingUser, setIsEditing, userStatus }) => { return ( @@ -30,14 +24,14 @@ const ProjectsSkills = ({ showingUser }) => { About me - - - + {userStatus === 'same' && ( + setIsEditing('description')} fill={true.toString()}> + + + )} {showingUser?.description ? ( - + +