Migrate to a monorepo structure (#2909)
This commit is contained in:
@ -0,0 +1,69 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
|
||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
|
||||
import { useFavorites } from '../hooks/useFavorites';
|
||||
|
||||
const StyledContainer = styled(NavigationDrawerSection)`
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const Favorites = () => {
|
||||
const { favorites, handleReorderFavorite } = useFavorites({
|
||||
objectNamePlural: 'companies',
|
||||
});
|
||||
|
||||
if (!favorites || favorites.length === 0) return <></>;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<NavigationDrawerSectionTitle label="Favorites" />
|
||||
<DraggableList
|
||||
onDragEnd={handleReorderFavorite}
|
||||
draggableItems={
|
||||
<>
|
||||
{favorites.map((favorite, index) => {
|
||||
const {
|
||||
id,
|
||||
labelIdentifier,
|
||||
avatarUrl,
|
||||
avatarType,
|
||||
link,
|
||||
recordId,
|
||||
} = favorite;
|
||||
|
||||
return (
|
||||
<DraggableItem
|
||||
key={id}
|
||||
draggableId={id}
|
||||
index={index}
|
||||
itemComponent={
|
||||
<NavigationDrawerItem
|
||||
key={id}
|
||||
label={labelIdentifier}
|
||||
Icon={() => (
|
||||
<Avatar
|
||||
colorId={recordId}
|
||||
avatarUrl={avatarUrl}
|
||||
type={avatarType}
|
||||
placeholder={labelIdentifier}
|
||||
/>
|
||||
)}
|
||||
to={link}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,225 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { useOptimisticEvict } from '@/apollo/optimistic-effect/hooks/useOptimisticEvict';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { Favorite } from '@/favorites/types/Favorite';
|
||||
import { mapFavorites } from '@/favorites/utils/mapFavorites';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { getRecordOptimisticEffectDefinition } from '@/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
import { favoritesState } from '../states/favoritesState';
|
||||
|
||||
export const useFavorites = ({
|
||||
objectNamePlural,
|
||||
}: {
|
||||
objectNamePlural: string;
|
||||
}) => {
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const [favorites, setFavorites] = useRecoilState(favoritesState);
|
||||
|
||||
const {
|
||||
updateOneRecordMutation: updateOneFavoriteMutation,
|
||||
createOneRecordMutation: createOneFavoriteMutation,
|
||||
deleteOneRecordMutation: deleteOneFavoriteMutation,
|
||||
objectMetadataItem: favoriteObjectMetadataItem,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular: 'favorite',
|
||||
});
|
||||
|
||||
const { registerOptimisticEffect, triggerOptimisticEffects } =
|
||||
useOptimisticEffect({
|
||||
objectNameSingular: 'favorite',
|
||||
});
|
||||
const { performOptimisticEvict } = useOptimisticEvict();
|
||||
|
||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const { objectMetadataItem: favoriteTargetObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
useFindManyRecords({
|
||||
objectNameSingular: 'favorite',
|
||||
onCompleted: useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (data: PaginatedRecordTypeResults<Required<Favorite>>) => {
|
||||
const favorites = snapshot.getLoadable(favoritesState).getValue();
|
||||
|
||||
const queriedFavorites = mapFavorites(
|
||||
data.edges.map((edge) => edge.node),
|
||||
);
|
||||
|
||||
if (!isDeeplyEqual(favorites, queriedFavorites)) {
|
||||
set(favoritesState, queriedFavorites);
|
||||
}
|
||||
|
||||
if (!favoriteObjectMetadataItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
registerOptimisticEffect({
|
||||
variables: { filter: {}, orderBy: {} },
|
||||
definition: getRecordOptimisticEffectDefinition({
|
||||
objectMetadataItem: favoriteObjectMetadataItem,
|
||||
}),
|
||||
});
|
||||
},
|
||||
[favoriteObjectMetadataItem, registerOptimisticEffect],
|
||||
),
|
||||
});
|
||||
|
||||
const createFavorite = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (favoriteTargetObjectId: string, additionalData?: any) => {
|
||||
const favorites = snapshot.getLoadable(favoritesState).getValue();
|
||||
|
||||
if (!favoriteTargetObjectMetadataItem) {
|
||||
return;
|
||||
}
|
||||
const targetObjectName = favoriteTargetObjectMetadataItem.nameSingular;
|
||||
|
||||
const result = await apolloClient.mutate({
|
||||
mutation: createOneFavoriteMutation,
|
||||
variables: {
|
||||
input: {
|
||||
[`${targetObjectName}Id`]: favoriteTargetObjectId,
|
||||
position: favorites.length + 1,
|
||||
workspaceMemberId: currentWorkspaceMember?.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
triggerOptimisticEffects(`FavoriteEdge`, result.data[`createFavorite`]);
|
||||
|
||||
const createdFavorite = result?.data?.createFavorite;
|
||||
|
||||
const newFavorite = {
|
||||
...additionalData,
|
||||
...createdFavorite,
|
||||
};
|
||||
|
||||
const newFavoritesMapped = mapFavorites([newFavorite]);
|
||||
|
||||
if (createdFavorite) {
|
||||
set(favoritesState, [...favorites, ...newFavoritesMapped]);
|
||||
}
|
||||
},
|
||||
[
|
||||
apolloClient,
|
||||
createOneFavoriteMutation,
|
||||
currentWorkspaceMember?.id,
|
||||
favoriteTargetObjectMetadataItem,
|
||||
triggerOptimisticEffects,
|
||||
],
|
||||
);
|
||||
|
||||
const _updateFavoritePosition = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (favoriteToUpdate: Favorite) => {
|
||||
const favoritesStateFromSnapshot = snapshot.getLoadable(favoritesState);
|
||||
const favorites = favoritesStateFromSnapshot.getValue();
|
||||
const result = await apolloClient.mutate({
|
||||
mutation: updateOneFavoriteMutation,
|
||||
variables: {
|
||||
input: {
|
||||
position: favoriteToUpdate?.position,
|
||||
},
|
||||
idToUpdate: favoriteToUpdate?.id,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedFavorite = result?.data?.updateFavoriteV2;
|
||||
if (updatedFavorite) {
|
||||
set(
|
||||
favoritesState,
|
||||
favorites.map((favorite: Favorite) =>
|
||||
favorite.id === updatedFavorite.id ? favoriteToUpdate : favorite,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
[apolloClient, updateOneFavoriteMutation],
|
||||
);
|
||||
|
||||
const deleteFavorite = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (favoriteIdToDelete: string) => {
|
||||
const favoritesStateFromSnapshot = snapshot.getLoadable(favoritesState);
|
||||
const favorites = favoritesStateFromSnapshot.getValue();
|
||||
const idToDelete = favorites.find(
|
||||
(favorite: Favorite) => favorite.recordId === favoriteIdToDelete,
|
||||
)?.id;
|
||||
|
||||
await apolloClient.mutate({
|
||||
mutation: deleteOneFavoriteMutation,
|
||||
variables: {
|
||||
idToDelete: idToDelete,
|
||||
},
|
||||
});
|
||||
|
||||
performOptimisticEvict('Favorite', 'id', idToDelete ?? '');
|
||||
|
||||
set(
|
||||
favoritesState,
|
||||
favorites.filter((favorite: Favorite) => favorite.id !== idToDelete),
|
||||
);
|
||||
},
|
||||
[apolloClient, deleteOneFavoriteMutation, performOptimisticEvict],
|
||||
);
|
||||
|
||||
const computeNewPosition = (destIndex: number, sourceIndex: number) => {
|
||||
if (destIndex === 0) {
|
||||
return favorites[destIndex].position / 2;
|
||||
}
|
||||
|
||||
if (destIndex === favorites.length - 1) {
|
||||
return favorites[destIndex - 1].position + 1;
|
||||
}
|
||||
|
||||
if (sourceIndex < destIndex) {
|
||||
return (
|
||||
(favorites[destIndex + 1].position + favorites[destIndex].position) / 2
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
(favorites[destIndex - 1].position + favorites[destIndex].position) / 2
|
||||
);
|
||||
};
|
||||
|
||||
const handleReorderFavorite: OnDragEndResponder = (result) => {
|
||||
if (!result.destination || !favorites) {
|
||||
return;
|
||||
}
|
||||
const newPosition = computeNewPosition(
|
||||
result.destination.index,
|
||||
result.source.index,
|
||||
);
|
||||
|
||||
const reorderFavorites = Array.from(favorites);
|
||||
const [removed] = reorderFavorites.splice(result.source.index, 1);
|
||||
const removedFav = { ...removed, position: newPosition };
|
||||
reorderFavorites.splice(result.destination.index, 0, removedFav);
|
||||
setFavorites(reorderFavorites);
|
||||
_updateFavoritePosition(removedFav);
|
||||
};
|
||||
return {
|
||||
favorites,
|
||||
createFavorite,
|
||||
deleteFavorite,
|
||||
handleReorderFavorite,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { Favorite } from '@/favorites/types/Favorite';
|
||||
|
||||
export const favoritesState = atom<Favorite[]>({
|
||||
key: 'favoritesState',
|
||||
default: [],
|
||||
});
|
||||
@ -0,0 +1,12 @@
|
||||
import { AvatarType } from '@/users/components/Avatar';
|
||||
|
||||
export type Favorite = {
|
||||
id: string;
|
||||
position: number;
|
||||
[key: string]: any;
|
||||
labelIdentifier: string;
|
||||
avatarUrl: string;
|
||||
avatarType: AvatarType;
|
||||
link: string;
|
||||
recordId: string;
|
||||
};
|
||||
@ -0,0 +1,38 @@
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
import { assertNotNull } from '~/utils/assert';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const mapFavorites = (favorites: any) => {
|
||||
return favorites
|
||||
.map((favorite: any) => {
|
||||
const recordInformation = isDefined(favorite?.person)
|
||||
? {
|
||||
id: favorite.person.id,
|
||||
labelIdentifier:
|
||||
favorite.person.name.firstName +
|
||||
' ' +
|
||||
favorite.person.name.lastName,
|
||||
avatarUrl: favorite.person.avatarUrl,
|
||||
avatarType: 'rounded',
|
||||
link: `/object/person/${favorite.person.id}`,
|
||||
}
|
||||
: isDefined(favorite?.company)
|
||||
? {
|
||||
id: favorite.company.id,
|
||||
labelIdentifier: favorite.company.name,
|
||||
avatarUrl: getLogoUrlFromDomainName(favorite.company.domainName),
|
||||
avatarType: 'squared',
|
||||
link: `/object/company/${favorite.company.id}`,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...recordInformation,
|
||||
recordId: recordInformation?.id,
|
||||
id: favorite?.id,
|
||||
position: favorite?.position,
|
||||
};
|
||||
})
|
||||
.filter(assertNotNull)
|
||||
.sort((a: any, b: any) => a.position - b.position);
|
||||
};
|
||||
Reference in New Issue
Block a user