adding more logic for creating a conversation
This commit is contained in:
@@ -1,11 +1,14 @@
|
|||||||
import { FieldValue, serverTimestamp, Timestamp } from 'firebase/firestore';
|
import { FieldValue, serverTimestamp, Timestamp } from 'firebase/firestore';
|
||||||
export default class Conversation {
|
export default class Conversation {
|
||||||
constructor(
|
id: string;
|
||||||
public id: string,
|
|
||||||
public name: string,
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
public createdByUserId: string,
|
public createdByUserId: string,
|
||||||
|
|
||||||
|
public membersList: string[],
|
||||||
|
|
||||||
|
public name?: string,
|
||||||
|
|
||||||
public lastUpdatedDate = Timestamp.now(),
|
public lastUpdatedDate = Timestamp.now(),
|
||||||
|
|
||||||
public createdDate = Timestamp.now(),
|
public createdDate = Timestamp.now(),
|
||||||
@@ -14,11 +17,11 @@ export default class Conversation {
|
|||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Member {
|
// export class Member {
|
||||||
constructor(
|
// constructor(
|
||||||
public id: string,
|
// public id: string,
|
||||||
public userId: string,
|
// public userId: string,
|
||||||
public role: 'admin' | 'regular',
|
// public role: 'admin' | 'regular',
|
||||||
public joinedDate = Timestamp.now(),
|
// public joinedDate = Timestamp.now(),
|
||||||
) {}
|
// ) {}
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Timestamp } from 'firebase/firestore';
|
import { Timestamp } from 'firebase/firestore';
|
||||||
|
|
||||||
export class User {
|
export class User {
|
||||||
|
id: string;
|
||||||
lastUpdatedDate?: Timestamp;
|
lastUpdatedDate?: Timestamp;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.63",
|
"@electron-forge/maker-squirrel": "^6.0.0-beta.63",
|
||||||
"@electron-forge/maker-zip": "^6.0.0-beta.63",
|
"@electron-forge/maker-zip": "^6.0.0-beta.63",
|
||||||
"@electron-forge/plugin-webpack": "6.0.0-beta.63",
|
"@electron-forge/plugin-webpack": "6.0.0-beta.63",
|
||||||
|
"@nirvana/core": "*",
|
||||||
"@types/react": "^18.0.9",
|
"@types/react": "^18.0.9",
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/react-dom": "^18.0.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||||
@@ -89,8 +90,7 @@
|
|||||||
"node-loader": "^2.0.0",
|
"node-loader": "^2.0.0",
|
||||||
"style-loader": "^3.0.0",
|
"style-loader": "^3.0.0",
|
||||||
"ts-loader": "^9.2.2",
|
"ts-loader": "^9.2.2",
|
||||||
"typescript": "~4.5.4",
|
"typescript": "~4.5.4"
|
||||||
"@nirvana/core": "*"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.9.0",
|
"@emotion/react": "^11.9.0",
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Box,
|
Box,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
ListItemSecondaryAction,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { blueGrey } from '@mui/material/colors';
|
import { blueGrey } from '@mui/material/colors';
|
||||||
import { FiActivity, FiInbox, FiSearch, FiUsers } from 'react-icons/fi';
|
import { FiActivity, FiInbox, FiSearch, FiUsers, FiCoffee } from 'react-icons/fi';
|
||||||
import NirvanaAvatar from './NirvanaAvatar';
|
import NirvanaAvatar from './NirvanaAvatar';
|
||||||
import { useDebounce, useKey } from 'react-use';
|
import { useDebounce, useKey } from 'react-use';
|
||||||
import { useSnackbar } from 'notistack';
|
import { useSnackbar } from 'notistack';
|
||||||
@@ -114,6 +115,8 @@ const Conversations = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function ListPeople({ people }: { people: User[] }) {
|
function ListPeople({ people }: { people: User[] }) {
|
||||||
|
const { handleQuickDial } = useTerminal();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
sx={{
|
sx={{
|
||||||
@@ -133,14 +136,16 @@ function ListPeople({ people }: { people: User[] }) {
|
|||||||
|
|
||||||
{people.map((person) => (
|
{people.map((person) => (
|
||||||
<ListItem key={`${person.uid}-searchUsers`}>
|
<ListItem key={`${person.uid}-searchUsers`}>
|
||||||
<ListItemButton selected={true}>
|
<ListItemButton onClick={() => handleQuickDial(person.uid)}>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar alt={person.displayName} src={person.photoUrl} />
|
<Avatar alt={person.displayName} src={person.photoUrl} />
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
|
|
||||||
<ListItemText primary={person.displayName} />
|
<ListItemText primary={person.displayName} />
|
||||||
|
|
||||||
<Typography variant={'caption'}>20 sec</Typography>
|
<ListItemSecondaryAction sx={{ color: 'GrayText' }}>
|
||||||
|
<FiCoffee />
|
||||||
|
</ListItemSecondaryAction>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { Container } from '@mui/system';
|
import { Container } from '@mui/system';
|
||||||
import { Avatar, Box, Fab, Grid, IconButton, Paper, Stack, Typography } from '@mui/material';
|
import { Avatar, Box, Fab, Grid, IconButton, Paper, Stack, Typography } from '@mui/material';
|
||||||
@@ -9,10 +9,14 @@ import { useSnackbar } from 'notistack';
|
|||||||
import Conversations from './Conversations';
|
import Conversations from './Conversations';
|
||||||
import Navbar from './Navbar';
|
import Navbar from './Navbar';
|
||||||
import Conversation from '@nirvana/core/src/models/conversation.model';
|
import Conversation from '@nirvana/core/src/models/conversation.model';
|
||||||
|
import useAuth from '../providers/AuthProvider';
|
||||||
|
import { createOneOnOneConversation } from '../firebase/firestore';
|
||||||
|
|
||||||
interface ITerminalContext {
|
interface ITerminalContext {
|
||||||
conversations: Conversation[];
|
conversations: Conversation[];
|
||||||
selectedConversation?: string;
|
selectedConversation?: Conversation;
|
||||||
|
|
||||||
|
handleQuickDial?: (otherUserId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TerminalContext = React.createContext<ITerminalContext>({
|
const TerminalContext = React.createContext<ITerminalContext>({
|
||||||
@@ -37,11 +41,56 @@ const TerminalContext = React.createContext<ITerminalContext>({
|
|||||||
|
|
||||||
export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
const [selectedConversationId, setSelectedConversationId] = useState<string>(undefined);
|
||||||
|
|
||||||
const [conversations, setConversations] = useState<Conversation[]>([]);
|
const [conversations, setConversations] = useState<Conversation[]>([]);
|
||||||
|
|
||||||
|
// cache of selected conversation
|
||||||
|
|
||||||
|
const selectedConversation: Conversation | undefined = useMemo(() => {
|
||||||
|
// get conversation if not here
|
||||||
|
// select if we do have it
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, [selectedConversationId]);
|
||||||
|
|
||||||
|
// handle create or open existing conversation
|
||||||
|
// not 100% consistent to the second, but still works...don't need atomicity
|
||||||
|
const handleQuickDial = useCallback(
|
||||||
|
async (otherUserId: string) => {
|
||||||
|
try {
|
||||||
|
// check all current conversations which should be live listened to
|
||||||
|
|
||||||
|
// if there is already a convo with exactly me and him, then select it
|
||||||
|
const findExistingConversation = conversations.find((convo) => {
|
||||||
|
if (
|
||||||
|
convo.membersInRoom?.length === 2 &&
|
||||||
|
convo.membersInRoom.includes(user.uid) &&
|
||||||
|
convo.membersInRoom.includes(otherUserId)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (findExistingConversation) {
|
||||||
|
setSelectedConversationId(findExistingConversation.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create conversation in this case
|
||||||
|
await createOneOnOneConversation(otherUserId, user.uid);
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar('Something went wrong, please try again', { variant: 'error' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[conversations, enqueueSnackbar, user],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TerminalContext.Provider value={{ selectedConversation: undefined, conversations }}>
|
<TerminalContext.Provider value={{ selectedConversation, conversations, handleQuickDial }}>
|
||||||
<Grid container spacing={0}>
|
<Grid container spacing={0}>
|
||||||
<Grid
|
<Grid
|
||||||
item
|
item
|
||||||
|
|||||||
@@ -21,24 +21,38 @@ import {
|
|||||||
import { User as FirebaseUser } from 'firebase/auth';
|
import { User as FirebaseUser } from 'firebase/auth';
|
||||||
import { firestoreDb } from './connect';
|
import { firestoreDb } from './connect';
|
||||||
import { User } from '@nirvana/core/src/models/user.model';
|
import { User } from '@nirvana/core/src/models/user.model';
|
||||||
|
import Conversation from '@nirvana/core/src/models/conversation.model';
|
||||||
|
|
||||||
|
interface Document {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UTILS
|
* UTILS
|
||||||
*/
|
*/
|
||||||
const converter = <T>() => ({
|
const converter = <T extends Document>() => ({
|
||||||
toFirestore: (data: T) => ({ ...data }),
|
toFirestore: (data: T) => ({ ...data }),
|
||||||
fromFirestore: (snap: QueryDocumentSnapshot) => snap.data() as T,
|
fromFirestore: (snap: QueryDocumentSnapshot) => {
|
||||||
|
const data = snap.data() as T;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
data.id = snap.id;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const docPoint = <T>(collectionPath: string) =>
|
const docPoint = <T extends Document>(collectionPath: string) =>
|
||||||
firestoreDb && doc(getFirestore(), collectionPath).withConverter(converter<T>());
|
firestoreDb && doc(getFirestore(), collectionPath).withConverter(converter<T>());
|
||||||
|
|
||||||
const collectionPoint = <T>(collectionPath: string) =>
|
const collectionPoint = <T extends Document>(collectionPath: string) =>
|
||||||
firestoreDb && collection(getFirestore(), collectionPath).withConverter(converter<T>());
|
firestoreDb && collection(getFirestore(), collectionPath).withConverter(converter<T>());
|
||||||
|
|
||||||
const db = {
|
const db = {
|
||||||
users: collectionPoint<User>(`users`),
|
users: collectionPoint<User>(`users`),
|
||||||
user: (userId: string) => docPoint<User>(`users/${userId}`),
|
user: (userId: string) => docPoint<User>(`users/${userId}`),
|
||||||
|
conversations: collectionPoint<Conversation>(`conversations`),
|
||||||
|
conversation: (conversationId: string) => docPoint<User>(`conversations/${conversationId}`),
|
||||||
};
|
};
|
||||||
|
|
||||||
// enum COLLECTION {
|
// enum COLLECTION {
|
||||||
@@ -97,3 +111,40 @@ export const searchUsers = async (searchQuery: string): Promise<User[] | undefin
|
|||||||
throw new Error('Error');
|
throw new Error('Error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createOneOnOneConversation = async (
|
||||||
|
otherUserId: string,
|
||||||
|
myUserId: string,
|
||||||
|
): Promise<string> => {
|
||||||
|
const conversation = new Conversation(myUserId, [myUserId, otherUserId]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await setDoc(
|
||||||
|
db.conversations(user.uid),
|
||||||
|
new User(
|
||||||
|
user.uid,
|
||||||
|
user.providerId,
|
||||||
|
user.email,
|
||||||
|
user.displayName,
|
||||||
|
user.photoURL,
|
||||||
|
user.phoneNumber,
|
||||||
|
),
|
||||||
|
{ merge: true },
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error creating user: ', e);
|
||||||
|
|
||||||
|
throw new Error('Error creating user');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
|
||||||
|
// const docRef =
|
||||||
|
// const docSnap = await getDoc(docRef);
|
||||||
|
|
||||||
|
// if (docSnap.exists()) {
|
||||||
|
// console.log("Document data:", docSnap.data());
|
||||||
|
// } else {
|
||||||
|
// // doc.data() will be undefined in this case
|
||||||
|
// console.log("No such document!");
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|||||||
@@ -1389,6 +1389,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/uuid@^8.3.4":
|
||||||
|
version "8.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||||
|
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||||
|
|
||||||
"@types/ws@^8.5.1":
|
"@types/ws@^8.5.1":
|
||||||
version "8.5.3"
|
version "8.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
|
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
|
||||||
|
|||||||
Reference in New Issue
Block a user