Skip to content

Commit

Permalink
refactor(contacts): Refactor contacts to efficient data fetching
Browse files Browse the repository at this point in the history
  • Loading branch information
TasoOneAsia committed Jun 8, 2021
1 parent 1940833 commit ecb71cc
Show file tree
Hide file tree
Showing 29 changed files with 417 additions and 352 deletions.
2 changes: 0 additions & 2 deletions phone/src/Phone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useSimcardService } from './os/simcard/hooks/useSimcardService';
import { usePhoneService } from './os/phone/hooks/usePhoneService';
import { useApps } from './os/apps/hooks/useApps';
import { useNuiRequest } from 'fivem-nui-react-lib';
import { useContactsService } from './apps/contacts/hooks/useContactsService';
import { useTwitterService } from './apps/twitter/hooks/useTwitterService';
import { useMatchService } from './apps/match/hooks/useMatchService';
import { useMarketplaceService } from './apps/marketplace/hooks/useMarketplaceService';
Expand Down Expand Up @@ -71,7 +70,6 @@ function Phone() {
useKeyboardService();
usePhoneService();
useSimcardService();
useContactsService();
useTwitterService();
useMatchService();
useMarketplaceService();
Expand Down
39 changes: 5 additions & 34 deletions phone/src/apps/contacts/components/ContactsApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { AppContent } from '../../../ui/components/AppContent';
import { useApp } from '../../../os/apps/hooks/useApps';
import InjectDebugData from '../../../os/debug/InjectDebugData';
import { Route } from 'react-router-dom';

import ContactsInfoPage from './views/ContactInfo';
import { ContactPage } from './views/ContactsPage';
import { ContactsThemeProvider } from '../providers/ContactsThemeProvider';
Expand All @@ -14,6 +13,7 @@ import PersonAddIcon from '@material-ui/icons/PersonAdd';
import Fab from '@material-ui/core/Fab';
import { useHistory } from 'react-router';
import { makeStyles, Theme } from '@material-ui/core/styles';
import { LoadingSpinner } from '../../../ui/components/LoadingSpinner';

const useStyles = makeStyles((theme: Theme) => ({
absolute: {
Expand All @@ -33,8 +33,10 @@ export const ContactsApp = () => {
<AppWrapper id="contact-app">
<AppTitle app={contacts} />
<AppContent>
<Route path="/contacts/" exact component={ContactPage} />
<Route path="/contacts/:id" exact component={ContactsInfoPage} />
<React.Suspense fallback={<LoadingSpinner />}>
<Route path="/contacts/" exact component={ContactPage} />
<Route path="/contacts/:id" exact component={ContactsInfoPage} />
</React.Suspense>
</AppContent>
<Fab
color="primary"
Expand All @@ -47,34 +49,3 @@ export const ContactsApp = () => {
</ContactsThemeProvider>
);
};

InjectDebugData([
{
app: 'CONTACTS',
method: ContactEvents.SEND_CONTACTS,
data: [
{
id: 1,
display: 'Ruqen',
number: '555-15196',
},
{
id: 2,
display: 'Taso',
number: '215-8139',
avatar: 'http://i.tasoagc.dev/i9Ig',
},
{
id: 3,
display: 'Chip',
number: '603-275-8373',
avatar: 'http://i.tasoagc.dev/2QYV',
},
{
id: 4,
display: 'Kidz',
number: '444-4444',
},
],
},
]);
56 changes: 23 additions & 33 deletions phone/src/apps/contacts/components/List/ContactList.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import React from 'react';
import ListItemText from '@material-ui/core/ListItemText';
import { Button, ListItemAvatar, Avatar as MuiAvatar, List, ListItem } from '@material-ui/core';
import { useFilteredContacts } from '../../hooks/useFilteredContacts';
import PhoneIcon from '@material-ui/icons/Phone';
import ChatIcon from '@material-ui/icons/Chat';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import { useContacts } from '../../hooks/useContacts';
import { SearchContacts } from './SearchContacts';
import { useHistory } from 'react-router-dom';
import LogDebugEvent from '../../../../os/debug/LogDebugEvents';
import { CallEvents } from '../../../../../../typings/call';
import { useNuiRequest } from 'fivem-nui-react-lib';
import { useFilteredContacts } from '../../hooks/state';

export const ContactList = () => {
const { filteredContacts } = useFilteredContacts();
const filteredContacts = useFilteredContacts();
const history = useHistory();
const Nui = useNuiRequest();

const { contacts } = useContacts();

const openContactInfo = (contactId: number) => {
history.push(`/contacts/${contactId}`);
};
Expand All @@ -43,38 +40,31 @@ export const ContactList = () => {
history.push(`/messages/new/${phoneNumber}`);
};

const filteredRegEx = new RegExp(filteredContacts, 'gi');

return (
<>
<SearchContacts />
<List>
{contacts
.filter(
(contact) =>
contact.display.match(filteredRegEx) || contact.number.match(filteredRegEx),
)
.map((contact) => (
<ListItem key={contact.id} divider>
<ListItemAvatar>
{contact.avatar ? (
<MuiAvatar src={contact.avatar} />
) : (
<MuiAvatar>{contact.display.slice(0, 1).toUpperCase()}</MuiAvatar>
)}
</ListItemAvatar>
<ListItemText primary={contact.display} secondary={contact.number} />
<Button onClick={() => startCall(contact.number)}>
<PhoneIcon />
</Button>
<Button onClick={() => handleMessage(contact.number)}>
<ChatIcon />
</Button>
<Button style={{ margin: -15 }} onClick={() => openContactInfo(contact.id)}>
<MoreVertIcon />
</Button>
</ListItem>
))}
{filteredContacts.map((contact) => (
<ListItem key={contact.id} divider>
<ListItemAvatar>
{contact.avatar ? (
<MuiAvatar src={contact.avatar} />
) : (
<MuiAvatar>{contact.display.slice(0, 1).toUpperCase()}</MuiAvatar>
)}
</ListItemAvatar>
<ListItemText primary={contact.display} secondary={contact.number} />
<Button onClick={() => startCall(contact.number)}>
<PhoneIcon />
</Button>
<Button onClick={() => handleMessage(contact.number)}>
<ChatIcon />
</Button>
<Button style={{ margin: -15 }} onClick={() => openContactInfo(contact.id)}>
<MoreVertIcon />
</Button>
</ListItem>
))}
</List>
</>
);
Expand Down
18 changes: 13 additions & 5 deletions phone/src/apps/contacts/components/List/SearchContacts.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Box } from '@material-ui/core';

import { useTranslation } from 'react-i18next';

import { useFilteredContacts } from '../../hooks/useFilteredContacts';
import { SearchField } from '../../../../ui/components/SearchField';
import { useDebounce } from '../../../../os/phone/hooks/useDebounce';
import { useSetContactFilterInput } from '../../hooks/state';

export const SearchContacts = () => {
const { setFilteredContacts, filteredContacts } = useFilteredContacts();
const { t } = useTranslation();
const setFilterVal = useSetContactFilterInput();
const [inputVal, setInputVal] = useState('');

const debouncedVal = useDebounce<string>(inputVal, 500);

useEffect(() => {
setFilterVal(debouncedVal);
}, [debouncedVal, setFilterVal]);

return (
<Box>
<SearchField
onChange={(e) => setFilteredContacts(e.target.value)}
onChange={(e) => setInputVal(e.target.value)}
placeholder={t('APPS_CONTACT_PLACEHOLDER_SEARCH_CONTACTS')}
value={filteredContacts}
value={inputVal}
/>
</Box>
);
Expand Down
72 changes: 61 additions & 11 deletions phone/src/apps/contacts/components/views/ContactInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { Avatar as MuiAvatar, Box, Button, Paper } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import { useContacts } from '../../hooks/useContacts';
import { useNuiRequest } from 'fivem-nui-react-lib';
import { useContactActions } from '../../hooks/useContactActions';
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
import LogDebugEvent from '../../../../os/debug/LogDebugEvents';
import { useQueryParams } from '../../../../common/hooks/useQueryParams';
import { ContactEvents } from '../../../../../../typings/contact';
import { Contact, ContactEvents } from '../../../../../../typings/contact';
import { TextField } from '../../../../ui/components/Input';
import { fetchNui } from '../../../../utils/fetchNui';
import { ServerPromiseResp } from '../../../../../../typings/common';
import { useSnackbar } from '../../../../ui/hooks/useSnackbar';

interface ContactInfoRouteParams {
mode: string;
Expand Down Expand Up @@ -49,11 +51,12 @@ const useStyles = makeStyles({
});

const ContactsInfoPage = () => {
const Nui = useNuiRequest();
const classes = useStyles();
const history = useHistory();

const { getContact } = useContacts();
const { getContact, addContact, updateContact, deleteContact } = useContactActions();

const { addAlert } = useSnackbar();

const { id } = useParams<ContactInfoRouteParams>();
const { addNumber, referal } = useQueryParams<ContactInfoRouteQuery>({
Expand Down Expand Up @@ -81,12 +84,27 @@ const ContactsInfoPage = () => {
data: contact,
level: 2,
});
Nui.send(ContactEvents.ADD_CONTACT, {
fetchNui<ServerPromiseResp<Contact>>(ContactEvents.ADD_CONTACT, {
display: name,
number,
avatar,
}).then((serverResp) => {
if (serverResp.status !== 'ok') {
return addAlert({
message: t('APPS_CONTACT_ADD_FAILED'),
type: 'error',
});
}

// Sanity checks maybe?

addContact(serverResp.data);
addAlert({
message: t('APPS_CONTACT_ADD_SUCCESS'),
type: 'error',
});
history.replace(referal);
});
history.replace(referal);
};

const handleContactSave = () => {
Expand All @@ -95,13 +113,33 @@ const ContactsInfoPage = () => {
data: contact,
level: 2,
});
Nui.send(ContactEvents.UPDATE_CONTACT, {
fetchNui<ServerPromiseResp>(ContactEvents.UPDATE_CONTACT, {
id: contact.id,
display: name,
number,
avatar,
}).then((resp) => {
if (resp.status !== 'ok') {
return addAlert({
message: t('APPS_CONTACT_UPDATE_FAILED'),
type: 'error',
});
}

updateContact({
id: contact.id,
display: name,
number,
avatar,
});

addAlert({
message: t('APPS_CONTACT_UPDATE_SUCCESS'),
type: 'success',
});

history.goBack();
});
history.goBack();
};

const handleContactDelete = () => {
Expand All @@ -110,8 +148,20 @@ const ContactsInfoPage = () => {
data: contact,
level: 2,
});
Nui.send(ContactEvents.DELETE_CONTACT, contact.id);
history.goBack();
fetchNui<ServerPromiseResp>(ContactEvents.DELETE_CONTACT, { id: contact.id }).then((resp) => {
if (resp.status !== 'ok') {
return addAlert({
message: t('APPS_CONTACT_DELETE_FAILED'),
type: 'error',
});
}
history.goBack();
deleteContact(contact.id);
addAlert({
message: t('APPS_CONTACT_DELETE_SUCCESS'),
type: 'error',
});
});
};

return (
Expand Down
58 changes: 49 additions & 9 deletions phone/src/apps/contacts/hooks/state.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,57 @@
import { atom } from 'recoil';
import { Contact } from '../../../../../typings/contact';
import { atom, selector, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { Contact, ContactEvents } from '../../../../../typings/contact';
import { fetchNui } from '../../../utils/fetchNui';
import { ServerPromiseResp } from '../../../../../typings/common';
import { isEnvBrowser } from '../../../utils/misc';
import LogDebugEvent from '../../../os/debug/LogDebugEvents';
import { BrowserContactsState } from '../utils/constants';

export const contactsState = {
contacts: atom<Contact[]>({
key: 'contactsList',
default: [],
default: selector({
key: 'contactsListDefault',
get: async () => {
try {
const resp = await fetchNui<ServerPromiseResp<Contact[]>>(ContactEvents.GET_CONTACTS);
LogDebugEvent({ action: 'ContactsFetched', data: resp.data });
return resp.data;
} catch (e) {
if (isEnvBrowser()) {
return BrowserContactsState;
}
console.error(e);
return [];
}
},
}),
}),
showModal: atom<boolean>({
key: 'showModal',
default: false,
}),
filterContacts: atom<string>({
key: 'filterContacts',
filterInput: atom<string>({
key: 'filterInput',
default: '',
}),
filteredContacts: selector({
key: 'filteredContacts',
get: ({ get }) => {
const filterInputVal: string = get(contactsState.filterInput);
const contacts: Contact[] = get(contactsState.contacts);

if (!filterInputVal) return contacts;

const regExp = new RegExp(filterInputVal, 'gi');

return contacts.filter(
(contact) => contact.display.match(regExp) || contact.number.match(regExp),
);
},
}),
};

export const useSetContacts = () => useSetRecoilState(contactsState.contacts);
export const useContacts = () => useRecoilState(contactsState.contacts);
export const useContactsValue = () => useRecoilValue(contactsState.contacts);

export const useFilteredContacts = () => useRecoilValue(contactsState.filteredContacts);

export const useContactFilterInput = () => useRecoilState(contactsState.filterInput);
export const useSetContactFilterInput = () => useSetRecoilState(contactsState.filterInput);
Loading

0 comments on commit ecb71cc

Please sign in to comment.