feat: Favorites (#1094)

* Adding the favorite button

* favorites services and resolvers

* favorites schema

* favorite ability handler

* favorite module export

* front end UI

* front end graphql additions

* server ability handlers

* server resolvers and services

* css fix

* Adding the favorite button

* favorites services and resolvers

* favorites schema

* favorite ability handler

* favorite module export

* front end UI

* front end graphql additions

* server ability handlers

* server resolvers and services

* css fix

* delete favorites handler and resolver

* removed favorite from index list

* chip avatar size props

* index list additions

* UI additions for favorites functionality

* lint fixes

* graphql codegen

* UI fixes

* favorite hook addition

* moved to ~/modules

* Favorite mapping to workspaceMember

* graphql codegen

* cosmetic changes

* camel cased methods

* graphql codegen
This commit is contained in:
Aditya Pimpalkar
2023-08-10 23:24:45 +01:00
committed by GitHub
parent d4b1153517
commit 0490c6b6ea
23 changed files with 917 additions and 21 deletions

View File

@ -19,6 +19,15 @@ export const GET_COMPANY = gql`
displayName
avatarUrl
}
Favorite {
id
person {
id
}
company {
id
}
}
}
}
`;

View File

@ -0,0 +1,63 @@
import styled from '@emotion/styled';
import NavItem from '@/ui/navbar/components/NavItem';
import { Avatar } from '@/users/components/Avatar';
import { useGetFavoritesQuery } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils';
const Wrapper = styled.div`
display: flex;
flex-direction: column;
overflow-x: auto;
width: 100%;
`;
export function Favorites() {
const { data } = useGetFavoritesQuery();
const favorites = data?.findFavorites;
if (!favorites) return <></>;
return (
<Wrapper>
{favorites &&
favorites.map(
({ id, person, company }) =>
(person && (
<NavItem
key={id}
label={`${person.firstName} ${person.lastName}`}
icon={
<Avatar
key={id}
avatarUrl={person.avatarUrl ?? ''}
type="rounded"
placeholder={`${person.firstName} ${person.lastName}`}
size="md"
/>
}
to={`/person/${person.id}`}
/>
)) ||
(company && (
<NavItem
key={id}
label={company.name}
icon={
<Avatar
key={id}
avatarUrl={
getLogoUrlFromDomainName(company.domainName) ?? ''
}
type="squared"
placeholder={company.name}
size="md"
/>
}
to={`/companies/${company.id}`}
/>
)),
)}
</Wrapper>
);
}

View File

@ -0,0 +1,84 @@
import { getOperationName } from '@apollo/client/utilities';
import { GET_COMPANY } from '@/companies/queries';
import { GET_PERSON } from '@/people/queries/show';
import {
useDeleteFavoriteMutation,
useInsertCompanyFavoriteMutation,
useInsertPersonFavoriteMutation,
} from '~/generated/graphql';
import { GET_FAVORITES } from '../queries/show';
export function useFavorites() {
const [insertCompanyFavoriteMutation] = useInsertCompanyFavoriteMutation();
const [insertPersonFavoriteMutation] = useInsertPersonFavoriteMutation();
const [deleteFavoriteMutation] = useDeleteFavoriteMutation();
function insertCompanyFavorite(companyId: string) {
insertCompanyFavoriteMutation({
variables: {
data: {
companyId,
},
},
refetchQueries: [
getOperationName(GET_FAVORITES) ?? '',
getOperationName(GET_COMPANY) ?? '',
],
});
}
function insertPersonFavorite(personId: string) {
insertPersonFavoriteMutation({
variables: {
data: {
personId,
},
},
refetchQueries: [
getOperationName(GET_FAVORITES) ?? '',
getOperationName(GET_PERSON) ?? '',
],
});
}
function deleteCompanyFavorite(companyId: string) {
deleteFavoriteMutation({
variables: {
where: {
companyId: {
equals: companyId,
},
},
},
refetchQueries: [
getOperationName(GET_FAVORITES) ?? '',
getOperationName(GET_COMPANY) ?? '',
],
});
}
function deletePersonFavorite(personId: string) {
deleteFavoriteMutation({
variables: {
where: {
personId: {
equals: personId,
},
},
},
refetchQueries: [
getOperationName(GET_FAVORITES) ?? '',
getOperationName(GET_PERSON) ?? '',
],
});
}
return {
insertCompanyFavorite,
insertPersonFavorite,
deleteCompanyFavorite,
deletePersonFavorite,
};
}

View File

@ -0,0 +1,25 @@
import { gql } from '@apollo/client';
export const GET_FAVORITES = gql`
query GetFavorites {
findFavorites {
id
person {
id
firstName
lastName
avatarUrl
}
company {
id
name
domainName
accountOwner {
id
displayName
avatarUrl
}
}
}
}
`;

View File

@ -0,0 +1,36 @@
import { gql } from '@apollo/client';
export const INSERT_PERSON_FAVORITE = gql`
mutation InsertPersonFavorite($data: FavoriteMutationForPersonArgs!) {
createFavoriteForPerson(data: $data) {
id
person {
id
firstName
lastName
displayName
}
}
}
`;
export const INSERT_COMPANY_FAVORITE = gql`
mutation InsertCompanyFavorite($data: FavoriteMutationForCompanyArgs!) {
createFavoriteForCompany(data: $data) {
id
company {
id
name
domainName
}
}
}
`;
export const DELETE_FAVORITE = gql`
mutation DeleteFavorite($where: FavoriteWhereInput!) {
deleteFavorite(where: $where) {
id
}
}
`;

View File

@ -23,6 +23,15 @@ export const GET_PERSON = gql`
name
domainName
}
Favorite {
id
person {
id
}
company {
id
}
}
}
}
`;

View File

@ -7,15 +7,18 @@ export type IconButtonSize = 'large' | 'medium' | 'small';
export type IconButtonFontColor = 'primary' | 'secondary' | 'tertiary';
export type IconButtonAccent = 'regular' | 'red';
export type ButtonProps = {
icon?: React.ReactNode;
variant?: IconButtonVariant;
size?: IconButtonSize;
textColor?: IconButtonFontColor;
accent?: IconButtonAccent;
} & React.ComponentProps<'button'>;
const StyledIconButton = styled.button<
Pick<ButtonProps, 'variant' | 'size' | 'textColor'>
Pick<ButtonProps, 'variant' | 'size' | 'textColor' | 'accent'>
>`
align-items: center;
background: ${({ theme, variant }) => {
@ -66,12 +69,14 @@ const StyledIconButton = styled.button<
return 'none';
}
}};
color: ${({ theme, disabled, textColor }) => {
color: ${({ theme, disabled, textColor, accent }) => {
if (disabled) {
return theme.font.color.extraLight;
}
return theme.font.color[textColor ?? 'secondary'];
return accent
? theme.color[accent]
: theme.font.color[textColor ?? 'secondary'];
}};
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
display: flex;
@ -121,6 +126,7 @@ export function IconButton({
size = 'medium',
textColor = 'tertiary',
disabled = false,
accent = 'regular',
...props
}: ButtonProps) {
return (
@ -129,6 +135,7 @@ export function IconButton({
size={size}
disabled={disabled}
textColor={textColor}
accent={accent}
{...props}
>
{icon}

View File

@ -51,6 +51,7 @@ export { IconUserCircle } from '@tabler/icons-react';
export { IconCalendar } from '@tabler/icons-react';
export { IconPencil } from '@tabler/icons-react';
export { IconCircleDot } from '@tabler/icons-react';
export { IconHeart } from '@tabler/icons-react';
export { IconBrandX } from '@tabler/icons-react';
export { IconTag } from '@tabler/icons-react';
export { IconHelpCircle } from '@tabler/icons-react';

View File

@ -10,8 +10,10 @@ type OwnProps = {
children: JSX.Element | JSX.Element[];
title: string;
hasBackButton?: boolean;
isFavorite?: boolean;
icon: ReactNode;
onAddButtonClick?: () => void;
onFavouriteButtonClick?: () => void;
};
const StyledContainer = styled.div`
@ -24,8 +26,10 @@ export function WithTopBarContainer({
children,
title,
hasBackButton,
isFavorite,
icon,
onAddButtonClick,
onFavouriteButtonClick,
}: OwnProps) {
return (
<StyledContainer>
@ -33,8 +37,10 @@ export function WithTopBarContainer({
<PageBar
title={title}
hasBackButton={hasBackButton}
isFavorite={isFavorite}
icon={icon}
onAddButtonClick={onAddButtonClick}
onFavouriteButtonClick={onFavouriteButtonClick}
/>
<RightDrawerContainer topMargin={PAGE_BAR_MIN_HEIGHT + 16 + 16}>
{children}

View File

@ -4,7 +4,7 @@ import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { IconButton } from '@/ui/button/components/IconButton';
import { IconChevronLeft, IconPlus } from '@/ui/icon/index';
import { IconChevronLeft, IconHeart, IconPlus } from '@/ui/icon/index';
import NavCollapseButton from '@/ui/navbar/components/NavCollapseButton';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
@ -58,18 +58,27 @@ const StyledTopBarIconTitleContainer = styled.div`
width: 100%;
`;
const ActionButtonsContainer = styled.div`
display: inline-flex;
gap: ${({ theme }) => theme.spacing(2)};
`;
type OwnProps = {
title: string;
hasBackButton?: boolean;
isFavorite?: boolean;
icon: ReactNode;
onAddButtonClick?: () => void;
onFavouriteButtonClick?: () => void;
};
export function PageBar({
title,
hasBackButton,
isFavorite,
icon,
onAddButtonClick,
onFavouriteButtonClick,
}: OwnProps) {
const navigate = useNavigate();
const navigateBack = useCallback(() => navigate(-1), [navigate]);
@ -104,16 +113,28 @@ export function PageBar({
</TitleContainer>
</StyledTopBarIconTitleContainer>
</StyledLeftContainer>
{onAddButtonClick && (
<IconButton
icon={<IconPlus size={16} />}
size="large"
data-testid="add-button"
textColor="secondary"
onClick={onAddButtonClick}
variant="border"
/>
)}
<ActionButtonsContainer>
{onFavouriteButtonClick && (
<IconButton
icon={<IconHeart size={16} />}
size="large"
data-testid="add-button"
accent={isFavorite ? 'red' : 'regular'}
onClick={onFavouriteButtonClick}
variant="border"
/>
)}
{onAddButtonClick && (
<IconButton
icon={<IconPlus size={16} />}
size="large"
data-testid="add-button"
textColor="secondary"
onClick={onAddButtonClick}
variant="border"
/>
)}
</ActionButtonsContainer>
</TopBarContainer>
</>
);