nirvana support really awesome right there!

This commit is contained in:
talksik
2022-05-30 10:45:58 -05:00
parent 946334dd60
commit 522cf40cf7
6 changed files with 147 additions and 186 deletions
@@ -1,25 +1,33 @@
/* eslint-disable jsx-a11y/media-has-caption */
import { blueGrey } from '@mui/material/colors';
import React, { useState, useEffect, useMemo } from 'react';
import { joinConversation, leaveConversation } from '../firebase/firestore';
import useAuth from '../providers/AuthProvider';
import { useImmer } from 'use-immer';
import useTerminal from './Terminal';
import { User } from '@nirvana/core/src/models/user.model';
import { import {
Stack,
IconButton,
Typography,
AvatarGroup,
Avatar, Avatar,
AvatarGroup,
Box, Box,
Container, Container,
Fab, Fab,
IconButton,
Paper, Paper,
Stack,
Typography,
} from '@mui/material'; } from '@mui/material';
import { FiSun, FiMoreVertical, FiPlay, FiCloudRain } from 'react-icons/fi'; import { FiCloudRain, FiMoreVertical, FiPlay, FiSun } from 'react-icons/fi';
import React, { useEffect, useMemo, useState } from 'react';
import { joinConversation, leaveConversation } from '../firebase/firestore';
import ConversationLabel from '../subcomponents/ConversationLabel'; import ConversationLabel from '../subcomponents/ConversationLabel';
import { User } from '@nirvana/core/src/models/user.model';
/* eslint-disable jsx-a11y/media-has-caption */
import { blueGrey } from '@mui/material/colors';
import useAuth from '../providers/AuthProvider';
import { useImmer } from 'use-immer';
import { useRendersCount } from 'react-use'; import { useRendersCount } from 'react-use';
import useTerminal from './Terminal';
/**
* nice convo history
* - chunk has max of 1 minute
* - put current clip in next chunk if previous clip was more than 4 hours ago
* - put current clip in next chunk if it's after my last active date here
*/
export default function ConversationDetails() { export default function ConversationDetails() {
const { user } = useAuth(); const { user } = useAuth();
@@ -1,38 +1,40 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { import {
Avatar, Avatar,
AvatarGroup,
Badge, Badge,
Box,
CircularProgress,
Divider, Divider,
IconButton,
Input,
List, List,
ListItem, ListItem,
ListItemAvatar, ListItemAvatar,
ListItemButton, ListItemButton,
ListItemSecondaryAction,
ListItemText, ListItemText,
ListSubheader, ListSubheader,
Stack, Stack,
Typography,
Input,
CircularProgress,
IconButton,
Box,
Tooltip, Tooltip,
ListItemSecondaryAction, Typography,
AvatarGroup,
} from '@mui/material'; } from '@mui/material';
import { blueGrey } from '@mui/material/colors'; import { FiActivity, FiCircle, FiCoffee, FiInbox, FiSearch, FiSun, FiUsers } from 'react-icons/fi';
import { FiActivity, FiInbox, FiSearch, FiUsers, FiCoffee, FiCircle, FiSun } from 'react-icons/fi'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import NirvanaAvatar from './NirvanaAvatar';
import { useDebounce, useKey, useRendersCount } from 'react-use'; import { useDebounce, useKey, useRendersCount } from 'react-use';
import { useSnackbar } from 'notistack';
import KeyboardShortcutLabel from './KeyboardShortcutLabel';
import { searchUsers } from '../firebase/firestore';
import { User } from '@nirvana/core/src/models/user.model';
import useTerminal from './Terminal';
import Conversation from '@nirvana/core/src/models/conversation.model'; import Conversation from '@nirvana/core/src/models/conversation.model';
import ConversationLabel from '../subcomponents/ConversationLabel';
import KeyboardShortcutLabel from './KeyboardShortcutLabel';
import NirvanaAvatar from './NirvanaAvatar';
import { User } from '@nirvana/core/src/models/user.model';
import { blueGrey } from '@mui/material/colors';
import { searchUsers } from '../firebase/firestore';
import useAuth from '../providers/AuthProvider'; import useAuth from '../providers/AuthProvider';
import { useImmer } from 'use-immer'; import { useImmer } from 'use-immer';
import ConversationLabel from '../subcomponents/ConversationLabel'; import { useSnackbar } from 'notistack';
import useTerminal from './Terminal';
// sort conversations based on the different data sources: type, conversations, audio clips, etc.
/** /**
* *
@@ -1,28 +1,29 @@
import { import {
Box,
Stack,
IconButton,
Avatar, Avatar,
Tooltip,
Switch,
Button,
AvatarGroup, AvatarGroup,
Box,
Button,
Divider,
Fab, Fab,
IconButton,
ListItemIcon, ListItemIcon,
Typography,
Menu, Menu,
MenuItem, MenuItem,
Stack,
Switch,
Tooltip,
Typography,
} from '@mui/material'; } from '@mui/material';
import { blueGrey } from '@mui/material/colors'; import { FiHeadphones, FiHelpCircle, FiLogOut, FiSun } from 'react-icons/fi';
import React, { useCallback } from 'react';
import React from 'react';
import { FiHeadphones, FiSun, FiLogOut } from 'react-icons/fi';
import ConversationLabel from '../subcomponents/ConversationLabel'; import ConversationLabel from '../subcomponents/ConversationLabel';
import useTerminal from './Terminal'; import { blueGrey } from '@mui/material/colors';
import useAuth from '../providers/AuthProvider'; import useAuth from '../providers/AuthProvider';
import useTerminal from './Terminal';
export default function FooterControls() { export default function FooterControls() {
const { selectedConversation } = useTerminal(); const { selectedConversation, handleOmniSearch } = useTerminal();
const { user, logout } = useAuth(); const { user, logout } = useAuth();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
@@ -34,6 +35,10 @@ export default function FooterControls() {
setAnchorEl(null); setAnchorEl(null);
}; };
const handleQuickDialSupport = useCallback(() => {
handleOmniSearch('Nirvana Support');
}, [handleOmniSearch]);
return ( return (
<Box <Box
sx={{ sx={{
@@ -158,36 +163,17 @@ export default function FooterControls() {
id="account-menu" id="account-menu"
open={open} open={open}
onClose={handleCloseProfileMenu} onClose={handleCloseProfileMenu}
onClick={handleCloseProfileMenu} transformOrigin={{ horizontal: 'right', vertical: 'bottom' }}
PaperProps={{ anchorOrigin={{ horizontal: 'right', vertical: 'top' }}
elevation: 0,
sx: {
overflow: 'visible',
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
mt: 1.5,
'& .MuiAvatar-root': {
width: 32,
height: 32,
ml: -0.5,
mr: 1,
},
'&:before': {
content: '""',
display: 'block',
position: 'absolute',
top: 0,
right: 14,
width: 10,
height: 10,
bgcolor: 'background.paper',
transform: 'translateY(-50%) rotate(45deg)',
zIndex: 0,
},
},
}}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
> >
<MenuItem onClick={handleQuickDialSupport}>
<ListItemIcon>
<FiHelpCircle />
</ListItemIcon>
<Typography>Help</Typography>
</MenuItem>
<Divider />
<MenuItem onClick={logout}> <MenuItem onClick={logout}>
<ListItemIcon> <ListItemIcon>
<FiLogOut /> <FiLogOut />
+43 -85
View File
@@ -1,36 +1,32 @@
import React, { useContext, useState, useCallback, useMemo, useEffect } from 'react';
import { import {
Avatar, Avatar,
AvatarGroup,
Box, Box,
Button,
Dialog,
Divider,
Fab,
Grid, Grid,
IconButton, IconButton,
Tooltip,
Stack,
Divider,
List, List,
ListItem, ListItem,
ListItemAvatar, ListItemAvatar,
Button,
ListItemButton, ListItemButton,
ListItemIcon,
ListItemSecondaryAction, ListItemSecondaryAction,
ListItemText, ListItemText,
ListSubheader, ListSubheader,
Typography,
Menu, Menu,
MenuItem, MenuItem,
ListItemIcon, Stack,
Fab,
Switch, Switch,
Dialog, Tooltip,
AvatarGroup, Typography,
} from '@mui/material'; } from '@mui/material';
import { blueGrey } from '@mui/material/colors'; import { ContentBlock, ContentType } from '@nirvana/core/src/models/content.model';
import { FiZap, FiHeadphones, FiLogOut, FiMonitor, FiSun } from 'react-icons/fi'; import { FiHeadphones, FiLogOut, FiMonitor, FiSun, FiZap } from 'react-icons/fi';
import { useSnackbar } from 'notistack'; import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { Unsubscribe, onSnapshot } from 'firebase/firestore';
import Conversation from '@nirvana/core/src/models/conversation.model';
import useAuth from '../providers/AuthProvider';
import { import {
createOneOnOneConversation, createOneOnOneConversation,
getConversationContentQueryLIVE, getConversationContentQueryLIVE,
@@ -39,21 +35,23 @@ import {
searchUsers, searchUsers,
sendContentBlockToConversation, sendContentBlockToConversation,
} from '../firebase/firestore'; } from '../firebase/firestore';
import { useImmer } from 'use-immer';
import { User } from '@nirvana/core/src/models/user.model';
import { onSnapshot, Unsubscribe } from 'firebase/firestore';
import { useDebounce, useKeyPressEvent, useRendersCount, useUnmount } from 'react-use'; import { useDebounce, useKeyPressEvent, useRendersCount, useUnmount } from 'react-use';
import { uploadAudioClip } from '../firebase/firebaseStorage'; import Conversation from '@nirvana/core/src/models/conversation.model';
import { ContentBlock, ContentType } from '@nirvana/core/src/models/content.model';
import Navbar from './Navbar';
import MainPanel from './MainPanel';
import { ConversationList } from './ConversationList';
import NewConversationDialog from './NewConversationDialog';
import { createGroupConversation } from '../firebase/firestore';
import ConversationLabel from '../subcomponents/ConversationLabel'; import ConversationLabel from '../subcomponents/ConversationLabel';
import { ConversationList } from './ConversationList';
import FooterControls from './FooterControls'; import FooterControls from './FooterControls';
import MainPanel from './MainPanel';
import Navbar from './Navbar';
import NewConversationDialog from './NewConversationDialog';
import { User } from '@nirvana/core/src/models/user.model';
import { blueGrey } from '@mui/material/colors';
import { createGroupConversation } from '../firebase/firestore';
import { uploadAudioClip } from '../firebase/firebaseStorage';
import useAuth from '../providers/AuthProvider';
import { useImmer } from 'use-immer';
import { useSnackbar } from 'notistack';
type ConversationMap = { type ConversationMap = {
[conversationId: string]: Conversation; [conversationId: string]: Conversation;
}; };
@@ -84,6 +82,8 @@ interface ITerminalContext {
handleShowCreateConvoForm?: () => void; handleShowCreateConvoForm?: () => void;
handleEscape?: () => void; handleEscape?: () => void;
handleOmniSearch?: (searchQuery: string) => void; // handle searching for anything globally
} }
const TerminalContext = React.createContext<ITerminalContext>({ const TerminalContext = React.createContext<ITerminalContext>({
@@ -106,60 +106,6 @@ const handleOnStartRecording = (e: BlobEvent) => {
audioChunks = []; audioChunks = [];
}; };
// TODOS
// fetch all conversations that I am part of
// create a convo
// 1. search
// 2. click on person
// 3. search database if there is already a convo for this
// - if yes: select this one
// - if no: create one
// 4. show the conversation details page show on right
// 5. connect live if other person there
// 6. send first clip
// 7. display all blocks/clips for convo simple and separate
// if no conversations, show stale state + create one with nirvana
// 1. create group conversation page
// you know what to do...simple row of selected users...dropdown options should show searched user list
// use material selects for this dropdown to avoid all the work...and hide select-like things
// 2. auto select that conversation and have it show up
// 3. allow adding name when creating convo
// 4.
// 5. convo settings: add people (simple add method with firestore), change name | max 8 people
/**
* Streaming stuff:
* - join a stream room if 2 or more in the list
* - leave on unmount
* - timer to unselect a conversation after 30 minutes...no fancy checking if user is there/active
* - show little control to see if
*/
/**
*
* media stuff:
* - paste image + view in history + with modal nice and big
* - paste a link + see in a content block
* - paste link of image and have it save as image
*/
/**
*
* tech debt: now go back and make convo line and side panel name and icons and all legit
*/
/**
* nice convo history
* - chunk has max of 1 minute
* - put current clip in next chunk if previous clip was more than 4 hours ago
* - put current clip in next chunk if it's after my last active date here
*/
// sort based on the different data sources: conversations, audio clips, etc.
// todo: extract each use effect to custom hook and will be clean // todo: extract each use effect to custom hook and will be clean
export function TerminalProvider({ children }: { children?: React.ReactNode }) { export function TerminalProvider({ children }: { children?: React.ReactNode }) {
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
@@ -493,14 +439,25 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
[searchVal, enqueueSnackbar, setSearchUsersResults, user], [searchVal, enqueueSnackbar, setSearchUsersResults, user],
); );
const handleChangeSearchInput = useCallback( const handleOmniSearch = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { (searchQuery: string) => {
if (!searchQuery) {
return;
}
setSearching(true); setSearching(true);
setSearchVal(e.target.value.replace('/', '')); setSearchVal(searchQuery.replace('/', ''));
}, },
[setSearching, setSearchVal], [setSearching, setSearchVal],
); );
const handleChangeSearchInput = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
handleOmniSearch(e.target.value);
},
[handleOmniSearch],
);
const selectConversation = useCallback( const selectConversation = useCallback(
(conversationId: string) => setSelectedConversationId(conversationId), (conversationId: string) => setSelectedConversationId(conversationId),
[setSelectedConversationId], [setSelectedConversationId],
@@ -571,6 +528,7 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
createConversationMode, createConversationMode,
handleShowCreateConvoForm, handleShowCreateConvoForm,
handleEscape, handleEscape,
handleOmniSearch,
}} }}
> >
<Stack direction={'column'} sx={{ flex: 1 }}> <Stack direction={'column'} sx={{ flex: 1 }}>
+29 -29
View File
@@ -1,39 +1,40 @@
//TODO: move all of this to core or common and separate out files //TODO: move all of this to core or common and separate out files
import {
setDoc,
getFirestore,
doc,
collection,
QueryDocumentSnapshot,
Timestamp,
FieldValue,
getDoc,
query,
where,
getDocs,
orderBy,
limit,
startAt,
endAt,
addDoc,
arrayUnion,
updateDoc,
arrayRemove,
serverTimestamp,
} from 'firebase/firestore';
import { User as FirebaseUser } from 'firebase/auth';
import { firestoreDb } from './connect';
import { User } from '@nirvana/core/src/models/user.model';
import Conversation, { import Conversation, {
ConversationMember, ConversationMember,
MemberMap, MemberMap,
MemberRole, MemberRole,
MemberState, MemberState,
} from '@nirvana/core/src/models/conversation.model'; } from '@nirvana/core/src/models/conversation.model';
import useAuth from '../providers/AuthProvider'; import {
FieldValue,
QueryDocumentSnapshot,
Timestamp,
addDoc,
arrayRemove,
arrayUnion,
collection,
doc,
endAt,
getDoc,
getDocs,
getFirestore,
limit,
orderBy,
query,
serverTimestamp,
setDoc,
startAt,
updateDoc,
where,
} from 'firebase/firestore';
import { ContentBlock } from '@nirvana/core/src/models/content.model'; import { ContentBlock } from '@nirvana/core/src/models/content.model';
import { User as FirebaseUser } from 'firebase/auth';
import { User } from '@nirvana/core/src/models/user.model';
import { firestoreDb } from './connect';
import { toTitleCase } from '../util/text';
import useAuth from '../providers/AuthProvider';
interface Document { interface Document {
id: string; id: string;
@@ -108,8 +109,7 @@ export const searchUsers = async (searchQuery: string): Promise<User[] | undefin
limit(5), limit(5),
); );
const nameManipulatedQuery = const nameManipulatedQuery = toTitleCase(searchQuery);
searchQuery[0].toUpperCase() + searchQuery.slice(1).toLocaleLowerCase();
const nameSearchQuery = query( const nameSearchQuery = query(
db.users, db.users,
orderBy('displayName'), orderBy('displayName'),
+7
View File
@@ -0,0 +1,7 @@
export const toTitleCase = (str: string) => {
return str
.toLowerCase()
.split(' ')
.map((s) => s.charAt(0).toUpperCase() + s.substring(1))
.join(' ');
};