Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction Inbox and Messaging (POC) #314

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
94f63a3
update entities for messaging and update gateway
skyfox93 Mar 31, 2023
525c850
update with new "inbox" methods
skyfox93 Apr 5, 2023
5ee7b6f
bugfix imports message entity
skyfox93 Apr 5, 2023
be36344
bugfix imports
skyfox93 Apr 5, 2023
f875787
undo transports
skyfox93 Apr 5, 2023
37d5e2f
uncomment send mail
skyfox93 Apr 5, 2023
c2d75c4
revert change to db connection service
skyfox93 Apr 5, 2023
eea8cbe
add controller methods
skyfox93 Apr 6, 2023
de6aa66
refactor user inbox
skyfox93 Apr 7, 2023
ed28d63
update inbox methods
skyfox93 Apr 7, 2023
b170905
fix org inbox
skyfox93 Apr 7, 2023
c4e6764
fix messages stub
skyfox93 Apr 7, 2023
b02bce0
Merge branch 'wip-messaging' of https://github.com/Nonprofit-Exchange…
skyfox93 Apr 7, 2023
e2d69df
bugfix nullable column organizationId
skyfox93 Apr 8, 2023
c6ca7b6
build proof of concept
skyfox93 Apr 12, 2023
beb49a4
updates to backend for messaging
skyfox93 Apr 12, 2023
5501cd7
bugfix imports for user entity
skyfox93 Apr 12, 2023
c40c7a9
bugfix imports
skyfox93 Apr 12, 2023
87c3fe1
bugfix import for message
skyfox93 Apr 12, 2023
d498dda
bugfix imports final
skyfox93 Apr 12, 2023
5a835f5
remove logger
skyfox93 Apr 12, 2023
68bbd69
update transactions with relations
skyfox93 Apr 12, 2023
6f352e9
update user stub relations
skyfox93 Apr 12, 2023
d067c33
update message entity
skyfox93 Apr 13, 2023
f35c96b
front end messaging
skyfox93 Apr 14, 2023
2535683
remove tempchat from routes
skyfox93 Apr 14, 2023
facd5f3
finish removing tempchat from routes
skyfox93 Apr 14, 2023
9d9cc71
undo change post-checkout
skyfox93 Apr 14, 2023
991b0ca
update transaction message card
skyfox93 Apr 14, 2023
f06ddb3
update messages stub
skyfox93 Apr 14, 2023
48c572c
updates to trasnsaction thread card
skyfox93 Apr 14, 2023
449a05c
make message targeted to transaction
skyfox93 Apr 15, 2023
40a7131
minor updates to styling chat
skyfox93 Apr 17, 2023
07dec53
show company image in chat
skyfox93 Apr 18, 2023
e278879
add handling for disconnected websocket
skyfox93 Apr 19, 2023
c7a10a6
Merge branch 'main' into wip-messaging
skyfox93 Apr 17, 2024
88324b9
update message seen functionality
skyfox93 Apr 18, 2024
315ffa4
bugfix-messages
skyfox93 Jun 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 4 additions & 26 deletions .husky/post-checkout
Original file line number Diff line number Diff line change
@@ -1,30 +1,8 @@
#!/bin/sh
FILE="$(dirname "$0")/_/husky.sh"

DIFF=$(git diff --numstat $1 -- server/package.json)
DIFF2=$(git diff --numstat $1 -- client/package.json)

# Check if the diff is empty
if [ -z "$DIFF" ]; then
echo 'Server Package.json unchanged'
else
if [ ! -f "$FILE" ]; then
cd server && npm ci --legacy-peer-deps && npm run prepare && cd ..
else
. "$FILE"
cd server && npm ci --legacy-peer-deps && cd ..
fi

if [ ! -f "$FILE" ]; then
cd server && npm ci --legacy-peer-deps && npm run prepare && cd ../client && npm ci --legacy-peer-deps && npm run prepare && cd ..
fi

if [ -z "$DIFF2" ]; then
echo ' Client Package.json unchanged'
else
if [ ! -f "$FILE" ]; then
cd client && npm ci --legacy-peer-deps && npm run prepare && cd ..
else
. "$FILE"
cd client && npm ci --legacy-peer-deps && cd ..
fi

fi
. "$FILE"
cd server && npm ci --legacy-peer-deps && cd ../client && npm ci --legacy-peer-deps && cd ..
4 changes: 4 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

cd server && npm run checks && cd ../client && npm run checks && cd ..
55 changes: 55 additions & 0 deletions client/src/FetchActions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { APP_API_BASE_URL } from './configs';
import { Message } from './types';

export const getRequest = (
url: string,
Expand Down Expand Up @@ -26,6 +27,42 @@ export const getRequest = (
}
});
};

export const patchRequest = (
url: string,
body: any,
onSuccess: Function,
abortController?: AbortController,
onError = (statusCode?: Number, statusText?: string) => {},
) => {
let options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
signal: abortController?.signal,
body: JSON.stringify(body),
};
fetch(url, options)
.then((resp) => {
if (!resp.ok) {
onError(resp.status, resp.statusText);
} else {
return resp.json();
}
})
.then((data) => onSuccess(data))
.catch((e) => {
if (e.name === 'AbortError') {
// usually, this abort is intentional due to a change in props
console.log('fetch aborted');
} else {
onError();
}
});
};
// best practice for fetching is to allow fetches to be aborted, in case props change while a fetch is in progress

export const fetchNeeds = (
limit: string,
offset: string,
Expand Down Expand Up @@ -54,3 +91,21 @@ export const fetchDonations = (

getRequest(assetsApiRequest.href + assetsApiRequest.hash, onSuccess, abortController, onError);
};

export const fetchInbox = (
onSuccess: Function,
abortController?: AbortController,
onError = (statusCode?: Number, statusText?: string) => {},
) => {
const assetsApiRequest = new URL(`${APP_API_BASE_URL}/transactions/inbox`);
getRequest(assetsApiRequest.href + assetsApiRequest.hash, onSuccess, abortController, onError);
};

export const updateMessage = (
messageBody: Message,
onSuccess: Function,
onError = (statusCode?: Number, statusText?: string) => {},
) => {
const messageApiRequest = new URL(`${APP_API_BASE_URL}/messages/${messageBody.id}`);
patchRequest(messageApiRequest.href, messageBody, onSuccess, new AbortController(), () => {});
};
2 changes: 1 addition & 1 deletion client/src/components/FAQs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function FAQs() {
return (
<div className={classes.faqs}>
<div className={classes.faqsVisual}>
<img src={FAQsImage} alt="FAQs Image" className={classes.faqsImage} />
<img src={FAQsImage} alt="FAQs" className={classes.faqsImage} />
</div>
<div className={classes.faqsInfo}>
<Typography variant="h4" component="h4">
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Users/Auth/SetNewPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ function SetNewPassword() {
return (
<div className={classes.container}>
<div className={classes.imgContainer}>
<img src={SetNewPasswordImg} alt="FAQs Image" className={classes.setNewPasswordImg} />
<img src={SetNewPasswordImg} alt="FAQs" className={classes.setNewPasswordImg} />
</div>
<Paper elevation={0} className={classes.paper}>
<Typography variant="h2" align="center">
Expand Down
28 changes: 14 additions & 14 deletions client/src/components/Users/Auth/SignUpCitizen/SignUpCitizen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,6 @@
}
}, [user]);

useEffect(() => {
if (submitForm) {
registerUserMutation.mutate(formData);
setSubmitForm(false);
}
}, [submitForm]);

useEffect(() => {
if (submitProfile) {
updateProfileMutation.mutate({ file: image!, userId: user!['id'] });
setSubmitProfile(false);
}
}, [submitProfile]);

const registerUserMutation = useMutation({
mutationFn: Endpoints.userRegister,
onSuccess: ({ data: user }) => {
Expand All @@ -82,6 +68,20 @@
onError: (error: AxiosError) => console.log(error),
});

useEffect(() => {
if (submitForm) {
setSubmitForm(false);
registerUserMutation.mutate(formData);
}
}, [submitForm, setSubmitForm, formData]);

Check warning on line 76 in client/src/components/Users/Auth/SignUpCitizen/SignUpCitizen.tsx

View workflow job for this annotation

GitHub Actions / linting

React Hook useEffect has a missing dependency: 'registerUserMutation'. Either include it or remove the dependency array

useEffect(() => {
if (submitProfile) {
updateProfileMutation.mutate({ file: image!, userId: user!['id'] });
setSubmitProfile(false);
}
}, [submitProfile, formData, image, user]);

Check warning on line 83 in client/src/components/Users/Auth/SignUpCitizen/SignUpCitizen.tsx

View workflow job for this annotation

GitHub Actions / linting

React Hook useEffect has a missing dependency: 'updateProfileMutation'. Either include it or remove the dependency array

const handleNext = (newFormData: {}, doSubmit = false) => {
setFormData((currFormData) => ({
...currFormData,
Expand Down
29 changes: 21 additions & 8 deletions client/src/components/Users/Inbox/MessageCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,52 @@ import { makeStyles } from 'tss-react/mui';

import type { Theme } from '@mui/material/styles';

import type { Message } from '../../../types';
import { Typography } from '@mui/material';
import { Button } from '@mui/base';

const useStyles = makeStyles()((theme: Theme) => ({
currentUserMessage: {
alignSelf: 'flex-end',
border: '1px solid black',
border: '1px solid lightgrey',
borderRadius: '10px',
padding: '5px',
maxWidth: '70%',
marginLeft: '30%',
background: 'rgba(196, 196, 196, 0.3)',
},
otherUserMessage: {
alignSelf: 'flex-start',
background: 'rgba(196, 196, 196, 0.3)',
border: '1px solid black',
background: 'lightblue',
border: '1px solid lightgrey',
borderRadius: '10px',
padding: '5px',
maxWidth: '70%',
},
}));

function MessageCard({
text,
senderName,
isCurrentUser,
message,
dateString,
messageReadCallback,
}: {
senderName: string;
text: string;
isCurrentUser: boolean;
message: Message;
dateString: string;
messageReadCallback: React.MouseEventHandler<HTMLButtonElement>;
}): JSX.Element {
const { classes } = useStyles();

return (
<div className={isCurrentUser ? classes.currentUserMessage : classes.otherUserMessage}>
{message.user.firstName}: {message.text}
<Typography variant="subtitle2" color="text.primary">
{text}
</Typography>
<Typography variant="body2" color="text.primary">
{dateString}
</Typography>
<Button onClick={messageReadCallback}> Read </Button>
</div>
);
}
Expand Down
126 changes: 73 additions & 53 deletions client/src/components/Users/Inbox/TransactionThreadCard.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,12 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import { makeStyles } from 'tss-react/mui';
import Typography from '@mui/material/Typography';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import PermIdentityRoundedIcon from '@mui/icons-material/PermIdentityRounded';

import type { Theme } from '@mui/material/styles';
import Divider from '@mui/material/Divider';
import ListItemText from '@mui/material/ListItemText';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import Avatar from '@mui/material/Avatar';

import type { Transaction, User } from '../../../types';

const useStyles = makeStyles()((theme: Theme) => ({
threadCardSelected: {
background: 'rgba(196, 196, 196, 0.3)',
width: '95%',
margin: '0 auto',
},
threadCard: {
background: 'white',
width: '95%',
margin: '0 auto',
boxShadow: 'none',
},
threadCardContent: {
padding: '10px 4px 10px 40px',
},
threadsSection: {
marginRight: '20px',
},
threadCardTitle: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
},
}));
import { ListItemButton } from '@mui/material';

function TransactionThreadCard({
isSelected,
Expand All @@ -44,30 +17,77 @@ function TransactionThreadCard({
isSelected: boolean;
onClick: (transaction: Transaction) => void;
transaction: Transaction;
user?: User;
user?: User | null;
}): JSX.Element {
const { classes } = useStyles();
const otherUser =
user?.id === transaction.requester.id
? transaction.requester.firstName
: transaction.donater.firstName;
const message = transaction.messages[0];
const renderMessage = () => {
if (message) {
return (
<React.Fragment>
<Typography
sx={{ display: 'inline' }}
component="span"
variant="body2"
color="text.primary"
>
{message.sendingUserId === (user && user.id) ? ' Me: ' : ''}
</Typography>
{message.text}
</React.Fragment>
);
}
};
const userOrg =
user && user.organizations && user.organizations[0] && user.organizations[0].organization.id;
const userIsClaimer = userOrg === transaction.claimer.id;
const isCurrentUser =
(userOrg && message && message.sendingOrgId === userOrg) ||
(user && message && message.sendingUserId === user.id);
const donaterIsOrg = !!transaction.donater_organization;

let otherUserName = '';
let otherUserImage = '' as string | undefined;
if (userIsClaimer) {
// other user is claimer
if (donaterIsOrg) {
const otherUser = transaction.donater_organization && transaction.donater_organization;
otherUserName = otherUser.name;
} else {
const otherUser = transaction.donater_user && transaction.donater_user;
otherUserName = otherUser.firstName;
otherUserImage = otherUser.profile_image_url;
}
} else {
// other user is claimer
otherUserName = transaction.claimer && transaction.claimer.name;
otherUserImage = transaction.claimer && transaction.claimer.image_url;
}

return (
<Card
className={isSelected ? classes.threadCardSelected : classes.threadCard}
onClick={() => onClick(transaction)}
variant={isSelected ? 'outlined' : undefined}
>
<CardContent className={classes.threadCardContent}>
<Box className={classes.threadCardTitle}>
<Typography variant="h6" component="h6">
{otherUser}
</Typography>
<PermIdentityRoundedIcon />
</Box>
<Typography>Re: {transaction.asset.title}</Typography>
</CardContent>
</Card>
<>
<ListItemButton
alignItems="flex-start"
selected={isSelected}
onClick={() => onClick(transaction)}
>
<ListItemAvatar>
{otherUserImage && <Avatar alt={otherUserName} src={otherUserImage} />}
</ListItemAvatar>
<ListItemText
primary={
<>
<Typography variant="subtitle2">
{message ? (isCurrentUser ? 'To : ' : 'From: ') : ''}
{otherUserName}
</Typography>
<Typography variant="subtitle2">{`Topic: ${transaction.asset.title}`}</Typography>
</>
}
secondary={renderMessage()}
/>
</ListItemButton>
<Divider variant="inset" component="li" />
</>
);
}

Expand Down
3 changes: 1 addition & 2 deletions client/src/providers/SettingsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,8 @@ export const SettingsProvider = ({ children }: Props) => {
const fetchCategories = async () => {
try {
const res = await fetch(`${APP_API_BASE_URL}/categories`);
const response = (await res.json()) satisfies Category[];
if (res.ok) {
_categoriesRef.current = [...response];
_categoriesRef.current = [...((await res.json()) satisfies Category[])];
}
} catch (error) {
console.log('Error fetching Categorie', error);
Expand Down
Loading
Loading