Skip to content

Commit

Permalink
Fix unnecessary multiples employees API requests (#2985)
Browse files Browse the repository at this point in the history
* Improve employee data handling during first load

* Fix <div> cannot descend from <p>

* Added logic to handle resending invites if the email has a prior invitation

* Refactor Text.P component formatting

* Improve timer error handling

* Added user feedback with toast notifications for team invites

* Standardize prop naming in Text component
  • Loading branch information
paradoxe35 authored Sep 5, 2024
1 parent cf02d9b commit 8928b13
Show file tree
Hide file tree
Showing 20 changed files with 163 additions and 35 deletions.
17 changes: 10 additions & 7 deletions apps/web/app/hooks/features/useEmployee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { useRecoilState } from 'recoil';
import { useQuery } from '../useQuery';
import { useAuthenticateUser } from './useAuthenticateUser';
import { IUpdateEmployee } from '@app/interfaces';
import { useFirstLoad } from '../useFirstLoad';

export const useEmployee = () => {
const { user } = useAuthenticateUser();
const [workingEmployees, setWorkingEmployees] = useRecoilState(workingEmployeesState);
const [workingEmployeesEmail, setWorkingEmployeesEmail] = useRecoilState(workingEmployeesEmailState);
const { firstLoad, firstLoadData: firstLoadDataEmployee } = useFirstLoad();

const { queryCall: getWorkingEmployeeQueryCall, loading: getWorkingEmployeeLoading } =
useQuery(getWorkingEmployeesAPI);
Expand All @@ -30,29 +32,30 @@ export const useEmployee = () => {
}, [getWorkingEmployeeQueryCall, setWorkingEmployees, setWorkingEmployeesEmail, user]);

useEffect(() => {
getWorkingEmployee();
}, [getWorkingEmployee]);
if (firstLoad) {
getWorkingEmployee();
}
}, [getWorkingEmployee, firstLoad]);

return {
firstLoadDataEmployee,
getWorkingEmployeeQueryCall,
getWorkingEmployeeLoading,
workingEmployees,
workingEmployeesEmail
};
};


export const useEmployeeUpdate = () => {
const { queryCall: employeeUpdateQuery, loading: isLoading } = useQuery(updateEmployeeAPI);

const updateEmployee = useCallback(({ id, data
}: { id: string, data: IUpdateEmployee }) => {
const updateEmployee = useCallback(({ id, data }: { id: string; data: IUpdateEmployee }) => {
employeeUpdateQuery({ id, data })
.then((res) => res.data)
.catch((error) => {
console.log(error);
});
}, []);

return { updateEmployee, isLoading }
}
return { updateEmployee, isLoading };
};
2 changes: 1 addition & 1 deletion apps/web/app/hooks/features/useTeamInvitations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export function useTeamInvitations() {

const resendTeamInvitation = useCallback(
(invitationId: string) => {
resendInviteQueryCall(invitationId);
return resendInviteQueryCall(invitationId);
},
[resendInviteQueryCall]
);
Expand Down
10 changes: 10 additions & 0 deletions apps/web/app/hooks/features/useTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,16 @@ export function useTimer() {
return;
});

promise.catch(() => {
if (taskId.current) {
updateLocalTimerStatus({
lastTaskId: taskId.current,
runnedDateTime: 0,
running: false
});
}
});

/**
* Updating the task status to "In Progress" when the timer is started.
*/
Expand Down
47 changes: 42 additions & 5 deletions apps/web/components/shared/invite/invite-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@ import React, { useState } from 'react';
import { IInvite, IInviteProps } from '../../../app/interfaces/hooks';
import { UserOutlineIcon } from 'assets/svg';
import { useTranslations } from 'next-intl';
import { useToast } from '@components/ui/use-toast';

const initalValues: IInvite = {
email: '',
name: ''
};
const InviteModal = ({ isOpen, Fragment, closeModal }: IInviteProps) => {
const [formData, setFormData] = useState<IInvite>(initalValues);
const { inviteUser, inviteLoading } = useTeamInvitations();
const { inviteUser, inviteLoading, teamInvitations, resendTeamInvitation, resendInviteLoading } =
useTeamInvitations();

const [errors, setErrors] = useState({});
const t = useTranslations();
const { toast } = useToast();

const isLoading = inviteLoading || resendInviteLoading;

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setErrors((er) => {
Expand All @@ -30,14 +37,44 @@ const InviteModal = ({ isOpen, Fragment, closeModal }: IInviteProps) => {

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const existingInvitation = teamInvitations.find((invitation) => invitation.email === formData.email);

if (existingInvitation) {
resendTeamInvitation(existingInvitation.id).then(() => {
closeModal();

toast({
variant: 'default',
title: t('common.INVITATION_SENT'),
description: t('common.INVITATION_SENT_TO_USER', { email: formData.email }),
duration: 5 * 1000
});
});
return;
}

inviteUser(formData.email, formData.name)
.then(() => {
.then((data) => {
setFormData(initalValues);
closeModal();
toast({
variant: 'default',
title: t('common.INVITATION_SENT'),
description: t('common.INVITATION_SENT_TO_USER', { email: formData.email }),
duration: 5 * 1000
});
})
.catch((err: AxiosError) => {
if (err.response?.status === 400) {
setErrors((err.response?.data as any)?.errors || {});
const data = err.response?.data as any;

if ('errors' in data) {
setErrors(data.errors || {});
}

if ('message' in data && Array.isArray(data.message)) {
setErrors({ email: data.message[0] });
}
}
});
};
Expand Down Expand Up @@ -109,10 +146,10 @@ const InviteModal = ({ isOpen, Fragment, closeModal }: IInviteProps) => {
<button
className="w-full flex justify-center items-center mt-10 px-4 font-bold h-[55px] py-2 rounded-[12px] tracking-wide text-white dark:text-primary transition-colors duration-200 transform bg-primary dark:bg-white hover:text-opacity-90 focus:outline-none text-[18px]"
type="submit"
disabled={inviteLoading}
disabled={isLoading}
>
<span>{t('pages.invite.INVITE_LABEL_SEND')}</span>{' '}
{inviteLoading && (
{isLoading && (
<span className="ml-2">
<Spinner />
</span>
Expand Down
4 changes: 4 additions & 0 deletions apps/web/lib/app/init-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
useTeamTasks,
useTimer
} from '@app/hooks';
import { useEmployee } from '@app/hooks/features/useEmployee';
import { useTimeLogs } from '@app/hooks/features/useTimeLogs';
import { publicState, userState } from '@app/stores';
// import { useSyncLanguage } from 'ni18n';
Expand Down Expand Up @@ -56,6 +57,8 @@ function InitState() {
const { firstLoadDailyPlanData } = useDailyPlan();
const { firstLoadTimeLogs } = useTimeLogs();

const { firstLoadDataEmployee } = useEmployee();

useOneTimeLoad(() => {
//To be called once, at the top level component (e.g main.tsx | _app.tsx);
firstLoadTeamsData();
Expand All @@ -75,6 +78,7 @@ function InitState() {
firstLoadTaskRelatedIssueTypeData();
firstLoadDailyPlanData();
firstLoadTimeLogs();
firstLoadDataEmployee();
// --------------

getTimerStatus();
Expand Down
19 changes: 16 additions & 3 deletions apps/web/lib/components/typography.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,25 @@ import { IVariant } from './types';
type Props = PropsWithChildren<IClassName>;

/**
* <p />
* <div />
*/
export const Text = ({ children, ...props }: Props & React.ComponentPropsWithRef<'p'>) => {
return <p {...props}>{children}</p>;
export const Text = ({ children, ...props }: Props & React.ComponentPropsWithRef<'div'>) => {
return <div {...props}>{children}</div>;
};

/**
* <P />
*/
Text.P = forwardRef<HTMLParagraphElement, Props & React.ComponentPropsWithRef<'p'>>(({ children, ...props }, ref) => {
return (
<p {...props} ref={ref}>
{children}
</p>
);
});

Text.P.displayName = 'TextParagraph';

/**
* <Link />
*/
Expand Down
47 changes: 41 additions & 6 deletions apps/web/lib/features/team/invite/invite-form-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,26 @@ import { BackButton, Button, Card, InputField, Modal, Text } from 'lib/component
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslations } from 'next-intl';
import { InviteEmailDropdown } from './invite-email-dropdown';
import { useToast } from '@components/ui/use-toast';

export function InviteFormModal({ open, closeModal }: { open: boolean; closeModal: () => void }) {
const t = useTranslations();
const { inviteUser, inviteLoading } = useTeamInvitations();
const { inviteUser, inviteLoading, teamInvitations, resendTeamInvitation, resendInviteLoading } =
useTeamInvitations();

const [errors, setErrors] = useState<{
email?: string;
name?: string;
}>({});

const [selectedEmail, setSelectedEmail] = useState<IInviteEmail>();
const { workingEmployees } = useEmployee();
const [currentOrgEmails, setCurrentOrgEmails] = useState<IInviteEmail[]>([]);
const { activeTeam } = useOrganizationTeams();
const nameInputRef = useRef<HTMLInputElement>(null);
const { toast } = useToast();

const isLoading = inviteLoading || resendInviteLoading;

useEffect(() => {
if (activeTeam?.members) {
Expand All @@ -40,7 +46,6 @@ export function InviteFormModal({ open, closeModal }: { open: boolean; closeModa
}, [workingEmployees, workingEmployees.length, activeTeam]);

const handleAddNew = (email: string) => {

if (!email.includes('@')) {
email = `${email}@gmail.com`;
}
Expand Down Expand Up @@ -70,15 +75,45 @@ export function InviteFormModal({ open, closeModal }: { open: boolean; closeModa
return;
}

inviteUser(selectedEmail.title, form.get('name')?.toString() || selectedEmail.name || '')
.then(() => {
const existingInvitation = teamInvitations.find((invitation) => invitation.email === selectedEmail.title);

if (existingInvitation) {
resendTeamInvitation(existingInvitation.id).then(() => {
closeModal();

toast({
variant: 'default',
title: t('common.INVITATION_SENT'),
description: t('common.INVITATION_SENT_TO_USER', { email: selectedEmail.title }),
duration: 5 * 1000
});
});
return;
}

inviteUser(selectedEmail.title, form.get('name')?.toString() || selectedEmail.name || '')
.then(({ data }) => {
closeModal();
e.currentTarget.reset();

toast({
variant: 'default',
title: t('common.INVITATION_SENT'),
description: t('common.INVITATION_SENT_TO_USER', { email: selectedEmail.title }),
duration: 5 * 1000
});
})
.catch((err: AxiosError) => {
if (err.response?.status === 400) {
setErrors((err.response?.data as any)?.errors || {});
const data = err.response?.data as any;

if ('errors' in data) {
setErrors(data.errors || {});
}

if ('message' in data && Array.isArray(data.message)) {
setErrors({ email: data.message[0] });
}
}
});
},
Expand Down Expand Up @@ -127,7 +162,7 @@ export function InviteFormModal({ open, closeModal }: { open: boolean; closeModa
<div className="flex items-center justify-between w-full mt-3">
<BackButton onClick={closeModal} />

<Button type="submit" disabled={inviteLoading} loading={inviteLoading}>
<Button type="submit" disabled={isLoading} loading={isLoading}>
{t('pages.invite.SEND_INVITE')}
</Button>
</div>
Expand Down
4 changes: 3 additions & 1 deletion apps/web/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@
"PAUSED": "متوقف",
"ONLINE": "متصل",
"SKIP_ADD_LATER": "أضف لاحقًا",
"DAILYPLAN": "مخطط"
"DAILYPLAN": "مخطط",
"INVITATION_SENT": "تم إرسال دعوة بنجاح",
"INVITATION_SENT_TO_USER": "تم إرسال دعوة فريق إلى {email}."
},
"hotkeys": {
"HELP": "مساعدة",
Expand Down
4 changes: 3 additions & 1 deletion apps/web/locales/bg.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@
"PAUSED": "Пауза",
"ONLINE": "Онлайн",
"SKIP_ADD_LATER": "Добави по-късно",
"DAILYPLAN": "Планирано"
"DAILYPLAN": "Планирано",
"INVITATION_SENT": "Покана е изпратена успешно",
"INVITATION_SENT_TO_USER": "Покана е изпратена на {email}."
},
"hotkeys": {
"HELP": "Помощ",
Expand Down
4 changes: 3 additions & 1 deletion apps/web/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@
"PAUSED": "Pausiert",
"ONLINE": "Online",
"SKIP_ADD_LATER": "Später hinzufügen",
"DAILYPLAN": "Plan dzienny"
"DAILYPLAN": "Plan dzienny",
"INVITATION_SENT": "Einladung erfolgreich gesendet",
"INVITATION_SENT_TO_USER": "Eine Teameinladung wurde an {email} gesendet."
},
"hotkeys": {
"HELP": "Hilfe",
Expand Down
4 changes: 3 additions & 1 deletion apps/web/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@
"PAUSED": "Paused",
"ONLINE": "Online",
"SKIP_ADD_LATER": "Add later",
"DAILYPLAN": "Planned"
"DAILYPLAN": "Planned",
"INVITATION_SENT": "Invitation Sent Successfully",
"INVITATION_SENT_TO_USER": "A team invitation has been sent to {email}."
},
"hotkeys": {
"HELP": "Help",
Expand Down
4 changes: 3 additions & 1 deletion apps/web/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@
"PAUSED": "Pausado",
"ONLINE": "En línea",
"SKIP_ADD_LATER": "Agregar más tarde",
"DAILYPLAN": "Planificado"
"DAILYPLAN": "Planificado",
"INVITATION_SENT": "Invitación enviada con éxito",
"INVITATION_SENT_TO_USER": "Se ha enviado una invitación de equipo a {email}."
},
"hotkeys": {
"HELP": "Ayuda",
Expand Down
4 changes: 3 additions & 1 deletion apps/web/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@
"PAUSED": "En pause",
"ONLINE": "En ligne",
"SKIP_ADD_LATER": "Ajouter plus tard",
"DAILYPLAN": "Planifié"
"DAILYPLAN": "Planifié",
"INVITATION_SENT": "Invitation envoyée avec succès",
"INVITATION_SENT_TO_USER": "Une invitation a été envoyée à {email}."
},
"hotkeys": {
"HELP": "Aide",
Expand Down
4 changes: 3 additions & 1 deletion apps/web/locales/he.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@
"PAUSED": "מושהה",
"ONLINE": "מחובר",
"SKIP_ADD_LATER": "הוסף מאוחר יותר",
"DAILYPLAN": "מְתוּכנָן"
"DAILYPLAN": "מְתוּכנָן",
"INVITATION_SENT": "ההזמנה נשלחה בהצלחה",
"INVITATION_SENT_TO_USER": "ההזמנה נשלחה ל{email}."
},
"hotkeys": {
"HELP": "עזרה",
Expand Down
4 changes: 3 additions & 1 deletion apps/web/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@
"PAUSED": "In Pausa",
"ONLINE": "Online",
"SKIP_ADD_LATER": "Aggiungi dopo",
"DAILYPLAN": "Pianificato"
"DAILYPLAN": "Pianificato",
"INVITATION_SENT": "Invito inviato con successo",
"INVITATION_SENT_TO_USER": "Un invito alla squadra è stato inviato a {email}."
},
"hotkeys": {
"HELP": "Aiuto",
Expand Down
Loading

0 comments on commit 8928b13

Please sign in to comment.