one page for easier state management

This commit is contained in:
talksik
2022-05-28 16:29:23 -05:00
parent cf10a4d1e4
commit 7ae7d7fa36
5 changed files with 265 additions and 254 deletions
@@ -7,7 +7,7 @@ export default class Conversation {
public membersList: string[],
public name?: string,
public name: string | null = null,
public lastUpdatedDate = Timestamp.now(),
@@ -29,232 +29,11 @@ import { searchUsers } from '../firebase/firestore';
import { User } from '@nirvana/core/src/models/user.model';
import useTerminal from './Terminal';
const Conversations = () => {
const { enqueueSnackbar } = useSnackbar();
// const Conversations = () => {
const searchRef = useRef<HTMLInputElement>(null);
const [searchVal, setSearchVal] = useState<string>('');
// return (
const onSearchFocus = useCallback(() => {
enqueueSnackbar('search focused');
if (searchRef?.current) searchRef.current.focus();
}, [enqueueSnackbar, searchRef]);
// );
// };
const [searchUsersResults, setSearchUsersResults] = useState<User[]>([]);
const [searching, setSearching] = useState<boolean>(false);
useKey('Shift', onSearchFocus);
const [, cancel] = useDebounce(
async () => {
if (searchVal) {
enqueueSnackbar('searching...', { variant: 'info' });
const results = await searchUsers(searchVal);
setSearchUsersResults(results);
console.warn(results);
}
setSearching(false);
},
500,
[searchVal, enqueueSnackbar, setSearchUsersResults],
);
const handleChangeSearchInput = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setSearching(true);
setSearchVal(e.target.value);
},
[setSearching, setSearchVal],
);
return (
<>
<Stack direction={'row'} alignItems="center" justifyContent={'flex-start'} spacing={1}>
<Stack
direction={'row'}
sx={{
position: 'relative',
display: 'flex',
flexDirection: 'row',
bgcolor: blueGrey[100],
borderRadius: 1,
px: 1,
py: 0.5,
flex: 1,
}}
alignItems={'center'}
spacing={1}
>
<FiSearch style={{ color: blueGrey[500] }} />
<Input
onChange={handleChangeSearchInput}
value={searchVal}
placeholder={'Find or start a conversation'}
inputRef={searchRef}
/>
{searching && <CircularProgress size={20} />}
<KeyboardShortcutLabel label="Shift" />
</Stack>
<Tooltip title={'Group conversation'}>
<IconButton color="secondary" size={'small'}>
<FiUsers />
</IconButton>
</Tooltip>
</Stack>
{searchVal ? <ListPeople people={searchUsersResults} /> : <ListConversations />}
</>
);
};
function ListPeople({ people }: { people: User[] }) {
const { handleQuickDial } = useTerminal();
return (
<List
sx={{
pt: 2,
}}
subheader={
<ListSubheader>
<Typography variant="subtitle2"> Search Results</Typography>
</ListSubheader>
}
>
{people.length === 0 && (
<Typography align="center" variant="caption">
{`Can't find someone? Invite them!`}
</Typography>
)}
{people.map((person) => (
<ListItem key={`${person.uid}-searchUsers`}>
<ListItemButton onClick={() => handleQuickDial(person.uid)}>
<ListItemAvatar>
<Avatar alt={person.displayName} src={person.photoUrl} />
</ListItemAvatar>
<ListItemText primary={person.displayName} />
<ListItemSecondaryAction sx={{ color: 'GrayText' }}>
<FiCoffee />
</ListItemSecondaryAction>
</ListItemButton>
</ListItem>
))}
</List>
);
}
function ListConversations() {
const { conversationMap } = useTerminal();
if (Object.keys(conversationMap).length === 0) {
return (
<Typography align="center" variant="caption">
Look for someone by name or email to start a conversation!
</Typography>
);
}
return (
<>
<List
sx={{
pt: 2,
}}
subheader={
<ListSubheader>
<FiActivity />
<Typography variant="subtitle2"> Priority</Typography>
</ListSubheader>
}
>
<ListItem>
<ListItemButton selected={true}>
<ListItemAvatar>
<Avatar alt={'Arjun Patel'} src="https://mui.com/static/images/avatar/2.jpg" />
</ListItemAvatar>
<ListItemText primary="Viet" />
<Typography variant={'caption'}>20 sec</Typography>
</ListItemButton>
</ListItem>
<ListItem>
<ListItemButton>
<ListItemAvatar>
<NirvanaAvatar
avatars={[
{ alt: 'Arjun Patel', src: 'https://mui.com/static/images/avatar/3.jpg' },
]}
/>
</ListItemAvatar>
<ListItemText primary="Agnes" />
<Typography variant={'caption'}>34 min ago</Typography>
</ListItemButton>
</ListItem>
</List>
<Divider />
<List
sx={{
pt: 2,
}}
subheader={
<ListSubheader>
<FiInbox />
<Typography variant="subtitle2"> Inbox</Typography>
</ListSubheader>
}
>
<ListItem>
<ListItemButton sx={{ opacity: 0.5 }}>
<ListItemAvatar>
<Avatar
sx={{
filter: `grayscale(1)`,
}}
alt={'Arjun Patel'}
src="https://mui.com/static/images/avatar/5.jpg"
/>
</ListItemAvatar>
<ListItemText secondary="Jeremy Leon" sx={{ color: blueGrey[300] }} />
<Badge color="primary" badgeContent=" " variant="dot"></Badge>
</ListItemButton>
</ListItem>
<ListItem>
<ListItemButton sx={{ opacity: 0.5 }}>
<ListItemAvatar>
<Avatar
sx={{
filter: `grayscale(1)`,
}}
alt={'Arjun Patel'}
src="https://mui.com/static/images/avatar/4.jpg"
/>
</ListItemAvatar>
<ListItemText secondary="James Lin" sx={{ color: blueGrey[300] }} />
<Typography variant={'caption'}>2 hours ago</Typography>
</ListItemButton>
</ListItem>
</List>
</>
);
}
export default Conversations;
// export default Conversations;
+2 -22
View File
@@ -1,40 +1,20 @@
import React, { useCallback, useRef } from 'react';
import React from 'react';
import { Container } from '@mui/system';
import {
Avatar,
Box,
Divider,
Fab,
Grid,
IconButton,
Input,
ListItemIcon,
Menu,
Paper,
Stack,
Tooltip,
Typography,
} from '@mui/material';
import NirvanaLogo from './NirvanaLogo';
import { blueGrey } from '@mui/material/colors';
import {
FiHeadphones,
FiLogOut,
FiMonitor,
FiMoreVertical,
FiPlay,
FiSearch,
FiSun,
FiWind,
} from 'react-icons/fi';
import KeyboardShortcutLabel from './KeyboardShortcutLabel';
import { useSnackbar } from 'notistack';
import { useKey } from 'react-use';
import { FiHeadphones, FiLogOut, FiMonitor, FiWind } from 'react-icons/fi';
import MenuItem from '@mui/material/MenuItem';
import useAuth from '../providers/AuthProvider';
import Conversations from './Conversations';
const Navbar = () => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
+256 -4
View File
@@ -1,12 +1,32 @@
import React, { useContext, useState, useCallback, useMemo, useEffect } from 'react';
import React, { useContext, useState, useCallback, useMemo, useEffect, useRef } from 'react';
import { Container } from '@mui/system';
import { Avatar, Box, Fab, Grid, IconButton, Paper, Stack, Typography } from '@mui/material';
import {
Avatar,
Box,
Fab,
Grid,
Paper,
Input,
CircularProgress,
IconButton,
Tooltip,
Stack,
Badge,
Divider,
List,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemSecondaryAction,
ListItemText,
ListSubheader,
Typography,
} from '@mui/material';
import { blueGrey } from '@mui/material/colors';
import { FiMoreVertical, FiPlay, FiSun } from 'react-icons/fi';
import { useSnackbar } from 'notistack';
import Conversations from './Conversations';
import Navbar from './Navbar';
import Conversation from '@nirvana/core/src/models/conversation.model';
import useAuth from '../providers/AuthProvider';
@@ -16,6 +36,13 @@ import { useImmer } from 'use-immer';
import { User } from '@nirvana/core/src/models/user.model';
import { onSnapshot } from 'firebase/firestore';
import { FiActivity, FiInbox, FiSearch, FiUsers, FiCoffee } from 'react-icons/fi';
import NirvanaAvatar from './NirvanaAvatar';
import { useDebounce, useKey } from 'react-use';
import KeyboardShortcutLabel from './KeyboardShortcutLabel';
import { searchUsers } from '../firebase/firestore';
type ConversationMap = {
[conversationId: string]: Conversation;
};
@@ -143,6 +170,44 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
[conversationMap, enqueueSnackbar, user],
);
const searchRef = useRef<HTMLInputElement>(null);
const [searchVal, setSearchVal] = useState<string>('');
const onSearchFocus = useCallback(() => {
enqueueSnackbar('search focused');
if (searchRef?.current) searchRef.current.focus();
}, [enqueueSnackbar, searchRef]);
const [searchUsersResults, setSearchUsersResults] = useState<User[]>([]);
const [searching, setSearching] = useState<boolean>(false);
useKey('Shift', onSearchFocus);
const [, cancel] = useDebounce(
async () => {
if (searchVal) {
enqueueSnackbar('searching...', { variant: 'info' });
const results = await searchUsers(searchVal);
setSearchUsersResults(results);
console.warn(results);
}
setSearching(false);
},
500,
[searchVal, enqueueSnackbar, setSearchUsersResults],
);
const handleChangeSearchInput = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setSearching(true);
setSearchVal(e.target.value);
},
[setSearching, setSearchVal],
);
return (
<TerminalContext.Provider
value={{ selectedConversation, conversationMap, contentMap, userMap, handleQuickDial }}
@@ -168,7 +233,51 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
>
<Navbar />
<Conversations />
<>
<Stack
direction={'row'}
alignItems="center"
justifyContent={'flex-start'}
spacing={1}
>
<Stack
direction={'row'}
sx={{
position: 'relative',
display: 'flex',
flexDirection: 'row',
bgcolor: blueGrey[100],
borderRadius: 1,
px: 1,
py: 0.5,
flex: 1,
}}
alignItems={'center'}
spacing={1}
>
<FiSearch style={{ color: blueGrey[500] }} />
<Input
onChange={handleChangeSearchInput}
value={searchVal}
placeholder={'Find or start a conversation'}
inputRef={searchRef}
/>
{searching && <CircularProgress size={20} />}
<KeyboardShortcutLabel label="Shift" />
</Stack>
<Tooltip title={'Group conversation'}>
<IconButton color="secondary" size={'small'}>
<FiUsers />
</IconButton>
</Tooltip>
</Stack>
{searchVal ? <ListPeople people={searchUsersResults} /> : <ListConversations />}
</>
</Stack>
</Grid>
@@ -335,3 +444,146 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
export default function useTerminal() {
return useContext(TerminalContext);
}
function ListPeople({ people }: { people: User[] }) {
const { handleQuickDial } = useTerminal();
return (
<List
sx={{
pt: 2,
}}
subheader={
<ListSubheader>
<Typography variant="subtitle2"> Search Results</Typography>
</ListSubheader>
}
>
{people.length === 0 && (
<Typography align="center" variant="caption">
{`Can't find someone? Invite them!`}
</Typography>
)}
{people.map((person) => (
<ListItem key={`${person.uid}-searchUsers`}>
<ListItemButton onClick={() => handleQuickDial(person.uid)}>
<ListItemAvatar>
<Avatar alt={person.displayName} src={person.photoUrl} />
</ListItemAvatar>
<ListItemText primary={person.displayName} />
<ListItemSecondaryAction sx={{ color: 'GrayText' }}>
<FiCoffee />
</ListItemSecondaryAction>
</ListItemButton>
</ListItem>
))}
</List>
);
}
function ListConversations() {
const { conversationMap } = useTerminal();
if (Object.keys(conversationMap).length === 0) {
return (
<Typography align="center" variant="caption">
Look for someone by name or email to start a conversation!
</Typography>
);
}
return (
<>
<List
sx={{
pt: 2,
}}
subheader={
<ListSubheader>
<FiActivity />
<Typography variant="subtitle2"> Priority</Typography>
</ListSubheader>
}
>
<ListItem>
<ListItemButton selected={true}>
<ListItemAvatar>
<Avatar alt={'Arjun Patel'} src="https://mui.com/static/images/avatar/2.jpg" />
</ListItemAvatar>
<ListItemText primary="Viet" />
<Typography variant={'caption'}>20 sec</Typography>
</ListItemButton>
</ListItem>
<ListItem>
<ListItemButton>
<ListItemAvatar>
<NirvanaAvatar
avatars={[
{ alt: 'Arjun Patel', src: 'https://mui.com/static/images/avatar/3.jpg' },
]}
/>
</ListItemAvatar>
<ListItemText primary="Agnes" />
<Typography variant={'caption'}>34 min ago</Typography>
</ListItemButton>
</ListItem>
</List>
<Divider />
<List
sx={{
pt: 2,
}}
subheader={
<ListSubheader>
<FiInbox />
<Typography variant="subtitle2"> Inbox</Typography>
</ListSubheader>
}
>
<ListItem>
<ListItemButton sx={{ opacity: 0.5 }}>
<ListItemAvatar>
<Avatar
sx={{
filter: `grayscale(1)`,
}}
alt={'Arjun Patel'}
src="https://mui.com/static/images/avatar/5.jpg"
/>
</ListItemAvatar>
<ListItemText secondary="Jeremy Leon" sx={{ color: blueGrey[300] }} />
<Badge color="primary" badgeContent=" " variant="dot"></Badge>
</ListItemButton>
</ListItem>
<ListItem>
<ListItemButton sx={{ opacity: 0.5 }}>
<ListItemAvatar>
<Avatar
sx={{
filter: `grayscale(1)`,
}}
alt={'Arjun Patel'}
src="https://mui.com/static/images/avatar/4.jpg"
/>
</ListItemAvatar>
<ListItemText secondary="James Lin" sx={{ color: blueGrey[300] }} />
<Typography variant={'caption'}>2 hours ago</Typography>
</ListItemButton>
</ListItem>
</List>
</>
);
}
+1 -1
View File
@@ -144,7 +144,7 @@ export const createOneOnOneConversation = async (
otherUserId: string,
myUserId: string,
): Promise<string | undefined> => {
const newConversation = new Conversation(myUserId, [myUserId, otherUserId]);
const newConversation = new Conversation(myUserId, [myUserId, otherUserId], null);
try {
const newDoc = await addDoc(db.conversations, newConversation);