react-garden
Version:
React + TypeScript + ThreeJS app using Material UI on NextJS, Apollo Client, GraphQL + WordPress REST APIs, for ThreeD web development.. a part of the threed.ai code family.
463 lines (437 loc) • 16.5 kB
JavaScript
// ** React Imports
import { useState, useEffect } from 'react'
// ** Next Imports
import { useRouter } from 'next/router'
// ** MUI Imports
import Box from '@mui/material/Box'
import List from '@mui/material/List'
import Chip from '@mui/material/Chip'
import Badge from '@mui/material/Badge'
import Drawer from '@mui/material/Drawer'
import MuiAvatar from '@mui/material/Avatar'
import ListItem from '@mui/material/ListItem'
import TextField from '@mui/material/TextField'
import IconButton from '@mui/material/IconButton'
import Typography from '@mui/material/Typography'
import ListItemText from '@mui/material/ListItemText'
import ListItemAvatar from '@mui/material/ListItemAvatar'
import ListItemButton from '@mui/material/ListItemButton'
import InputAdornment from '@mui/material/InputAdornment'
// ** Third Party Components
import PerfectScrollbar from 'react-perfect-scrollbar'
// ** Icons Imports
import Close from 'mdi-material-ui/Close'
import Magnify from 'mdi-material-ui/Magnify'
// ** Custom Components Import
import CustomAvatar from '~/@core/components/mui/avatar'
// ** Chat App Components Imports
import UserProfileLeft from '~/views/apps/chat/UserProfileLeft'
const ScrollWrapper = ({ children, hidden }) => {
if (hidden) {
return <Box sx={{ height: '100%', overflow: 'auto' }}>{children}</Box>
} else {
return <PerfectScrollbar options={{ wheelPropagation: false }}>{children}</PerfectScrollbar>
}
}
const SidebarLeft = props => {
// ** Props
const {
store,
hidden,
mdAbove,
dispatch,
statusObj,
userStatus,
selectChat,
getInitials,
sidebarWidth,
setUserStatus,
leftSidebarOpen,
removeSelectedChat,
userProfileLeftOpen,
formatDateToMonthShort,
handleLeftSidebarToggle,
handleUserProfileLeftSidebarToggle
} = props
// ** States
const [query, setQuery] = useState('')
const [filteredChat, setFilteredChat] = useState([])
const [filteredContacts, setFilteredContacts] = useState([])
const [active, setActive] = useState(null)
// ** Hooks
const router = useRouter()
const handleChatClick = (type, id) => {
dispatch(selectChat(id))
setActive({ type, id })
if (!mdAbove) {
handleLeftSidebarToggle()
}
}
useEffect(() => {
if (store && store.chats) {
if (active !== null) {
if (active.type === 'contact' && active.id === store.chats[0].id) {
setActive({ type: 'chat', id: active.id })
}
}
}
}, [store, active])
useEffect(() => {
router.events.on('routeChangeComplete', () => {
setActive(null)
dispatch(removeSelectedChat())
})
return () => {
setActive(null)
dispatch(removeSelectedChat())
}
}, [])
const hasActiveId = id => {
if (store.chats !== null) {
const arr = store.chats.filter(i => i.id === id)
return !!arr.length
}
}
const renderChats = () => {
if (store && store.chats && store.chats.length) {
if (query.length && !filteredChat.length) {
return (
<ListItem>
<Typography sx={{ color: 'text.secondary' }}>No Chats Found</Typography>
</ListItem>
)
} else {
const arrToMap = query.length && filteredChat.length ? filteredChat : store.chats
return arrToMap.map((chat, index) => {
const { lastMessage } = chat.chat
const activeCondition = active !== null && active.id === chat.id && active.type === 'chat'
return (
<ListItem key={index} disablePadding sx={{ '&:not(:last-child)': { mb: 1.5 } }}>
<ListItemButton
disableRipple
onClick={() => handleChatClick('chat', chat.id)}
sx={{
px: 3,
py: 2.5,
width: '100%',
borderRadius: 1,
alignItems: 'flex-start',
...(activeCondition && {
backgroundImage: theme =>
`linear-gradient(98deg, ${theme.palette.customColors.primaryGradient}, ${theme.palette.primary.main} 94%)`
})
}}
>
<ListItemAvatar sx={{ m: 0 }}>
<Badge
overlap='circular'
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right'
}}
badgeContent={
<Box
component='span'
sx={{
width: 8,
height: 8,
borderRadius: '50%',
color: `${statusObj[chat.status]}.main`,
backgroundColor: `${statusObj[chat.status]}.main`,
boxShadow: theme =>
`0 0 0 2px ${!activeCondition ? theme.palette.background.paper : theme.palette.common.white
}`
}}
/>
}
>
{chat.avatar ? (
<MuiAvatar
src={chat.avatar}
alt={chat.fullName}
sx={{
width: 38,
height: 38,
border: theme => (activeCondition ? `2px solid ${theme.palette.common.white}` : '')
}}
/>
) : (
<CustomAvatar
color={chat.avatarColor}
skin={activeCondition ? 'light-static' : 'light'}
sx={{
width: 38,
height: 38,
fontSize: '1rem',
border: theme => (activeCondition ? `2px solid ${theme.palette.common.white}` : '')
}}
>
{getInitials(chat.fullName)}
</CustomAvatar>
)}
</Badge>
</ListItemAvatar>
<ListItemText
sx={{
my: 0,
ml: 4,
mr: 1.5,
'& .MuiTypography-root': { ...(activeCondition ? { color: 'common.white' } : {}) }
}}
primary={
<Typography noWrap sx={{ fontWeight: 500, fontSize: '0.875rem' }}>
{chat.fullName}
</Typography>
}
secondary={
<Typography
noWrap
variant='body2'
sx={{ color: !activeCondition ? theme => theme.palette.text.disabled : {} }}
>
{lastMessage ? lastMessage.message : null}
</Typography>
}
/>
<Box
sx={{
display: 'flex',
alignItems: 'flex-end',
flexDirection: 'column',
justifyContent: 'flex-start'
}}
>
<Typography
variant='body2'
sx={{ whiteSpace: 'nowrap', color: activeCondition ? 'common.white' : 'text.disabled' }}
>
<>{lastMessage ? formatDateToMonthShort(lastMessage.time, true) : new Date()}</>
</Typography>
{chat.chat.unseenMsgs && chat.chat.unseenMsgs > 0 ? (
<Chip
color='error'
label={chat.chat.unseenMsgs}
sx={{
mt: 0.5,
height: 18,
fontWeight: 600,
fontSize: '0.75rem',
'& .MuiChip-label': { pt: 0.25, px: 1.655 }
}}
/>
) : null}
</Box>
</ListItemButton>
</ListItem>
)
})
}
}
}
const renderContacts = () => {
if (store && store.chats && store.chats.length) {
if (query.length && !filteredContacts.length) {
return (
<ListItem>
<Typography sx={{ color: 'text.secondary' }}>No Contacts Found</Typography>
</ListItem>
)
} else {
const arrToMap = query.length && filteredContacts.length ? filteredContacts : store.contacts
return arrToMap !== null
? arrToMap.map((contact, index) => {
const activeCondition =
active !== null && active.id === contact.id && active.type === 'contact' && !hasActiveId(contact.id)
return (
<ListItem key={index} disablePadding sx={{ '&:not(:last-child)': { mb: 1.5 } }}>
<ListItemButton
disableRipple
onClick={() => handleChatClick(hasActiveId(contact.id) ? 'chat' : 'contact', contact.id)}
sx={{
px: 3,
py: 2.5,
width: '100%',
borderRadius: 1,
...(activeCondition && {
backgroundImage: theme =>
`linear-gradient(98deg, ${theme.palette.customColors.primaryGradient}, ${theme.palette.primary.main} 94%)`
})
}}
>
<ListItemAvatar sx={{ m: 0 }}>
{contact.avatar ? (
<MuiAvatar
alt={contact.fullName}
src={contact.avatar}
sx={{
width: 38,
height: 38,
border: theme => (activeCondition ? `2px solid ${theme.palette.common.white}` : '')
}}
/>
) : (
<CustomAvatar
color={contact.avatarColor}
skin={activeCondition ? 'light-static' : 'light'}
sx={{
width: 38,
height: 38,
fontSize: '1rem',
border: theme => (activeCondition ? `2px solid ${theme.palette.common.white}` : '')
}}
>
{getInitials(contact.fullName)}
</CustomAvatar>
)}
</ListItemAvatar>
<ListItemText
sx={{ my: 0, ml: 4, '& .MuiTypography-root': { color: activeCondition ? 'common.white' : '' } }}
primary={
<Typography sx={{ fontWeight: 500, fontSize: '0.875rem' }}>{contact.fullName}</Typography>
}
secondary={
<Typography
noWrap
variant='body2'
sx={{ color: !activeCondition ? theme => theme.palette.text.disabled : {} }}
>
{contact.about}
</Typography>
}
/>
</ListItemButton>
</ListItem>
)
})
: null
}
}
}
const handleFilter = e => {
setQuery(e.target.value)
if (store.chats !== null && store.contacts !== null) {
const searchFilterFunction = contact => contact.fullName.toLowerCase().includes(e.target.value.toLowerCase())
const filteredChatsArr = store.chats.filter(searchFilterFunction)
const filteredContactsArr = store.contacts.filter(searchFilterFunction)
setFilteredChat(filteredChatsArr)
setFilteredContacts(filteredContactsArr)
}
}
return (
<Box>
<Drawer
open={leftSidebarOpen}
onClose={handleLeftSidebarToggle}
variant={mdAbove ? 'permanent' : 'temporary'}
ModalProps={{
disablePortal: true,
keepMounted: true // Better open performance on mobile.
}}
sx={{
zIndex: 7,
height: '100%',
display: 'block',
position: mdAbove ? 'static' : 'absolute',
'& .MuiDrawer-paper': {
boxShadow: 'none',
width: sidebarWidth,
position: mdAbove ? 'static' : 'absolute',
borderTopLeftRadius: theme => theme.shape.borderRadius,
borderBottomLeftRadius: theme => theme.shape.borderRadius
},
'& > .MuiBackdrop-root': {
borderRadius: 1,
position: 'absolute',
zIndex: theme => theme.zIndex.drawer - 1
}
}}
>
<Box
sx={{
py: 3,
px: 5,
display: 'flex',
alignItems: 'center',
borderBottom: theme => `1px solid ${theme.palette.divider}`
}}
>
{store && store.userProfile ? (
<Badge
overlap='circular'
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right'
}}
sx={{ mr: 4 }}
onClick={handleUserProfileLeftSidebarToggle}
badgeContent={
<Box
component='span'
sx={{
width: 8,
height: 8,
borderRadius: '50%',
color: `${statusObj[userStatus]}.main`,
backgroundColor: `${statusObj[userStatus]}.main`,
boxShadow: theme => `0 0 0 2px ${theme.palette.background.paper}`
}}
/>
}
>
<MuiAvatar
src={store.userProfile.avatar}
alt={store.userProfile.fullName}
sx={{ width: '2.375rem', height: '2.375rem', cursor: 'pointer' }}
/>
</Badge>
) : null}
<TextField
fullWidth
size='small'
value={query}
onChange={handleFilter}
placeholder='Search for contact...'
sx={{ '& .MuiInputBase-root': { borderRadius: 5 } }}
InputProps={{
startAdornment: (
<InputAdornment position='start' sx={{ color: 'text.secondary' }}>
<Magnify fontSize='small' />
</InputAdornment>
)
}}
/>
{!mdAbove ? (
<IconButton sx={{ p: 1, ml: 1 }} onClick={handleLeftSidebarToggle}>
<Close sx={{ fontSize: '1.375rem' }} />
</IconButton>
) : null}
</Box>
<Box sx={{ height: `calc(100% - 4.0625rem)` }}>
<ScrollWrapper hidden={hidden}>
<Box sx={{ p: theme => theme.spacing(7, 3, 3) }}>
<Typography variant='h6' sx={{ ml: 3, mb: 3, color: 'primary.main' }}>
Chats
</Typography>
<List sx={{ mb: 4, p: 0 }}>{renderChats()}</List>
<Typography variant='h6' sx={{ ml: 3, mb: 3, color: 'primary.main' }}>
Contacts
</Typography>
<List sx={{ p: 0 }}>{renderContacts()}</List>
</Box>
</ScrollWrapper>
</Box>
</Drawer>
<UserProfileLeft
store={store}
hidden={hidden}
statusObj={statusObj}
userStatus={userStatus}
sidebarWidth={sidebarWidth}
setUserStatus={setUserStatus}
userProfileLeftOpen={userProfileLeftOpen}
handleUserProfileLeftSidebarToggle={handleUserProfileLeftSidebarToggle}
/>
</Box>
)
}
export default SidebarLeft