Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { SidebarSectionProps } from './common';
import { useSquadPendingPosts } from '../../../hooks/squads/useSquadPendingPosts';
import { Typography, TypographyColor } from '../../typography/Typography';
import { SourcePostModerationStatus } from '../../../graphql/squads';
import { SquadFavoriteButton } from '../../squads/SquadFavoriteButton';

export const NetworkSection = ({
isItemsButton,
Expand Down Expand Up @@ -42,6 +43,7 @@ export const NetworkSection = ({
),
title: name,
path: `${webappUrl}squads/${handle}`,
rightIcon: () => <SquadFavoriteButton squad={squad} />,
};
}) ?? [];
return [
Expand Down
47 changes: 47 additions & 0 deletions packages/shared/src/components/squads/SquadFavoriteButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { MouseEvent, ReactElement } from 'react';
import React, { useCallback } from 'react';
import classNames from 'classnames';
import type { Squad } from '../../graphql/sources';
import { StarIcon } from '../icons';
import type { IconSize } from '../Icon';
import { useSquadFavorite } from '../../hooks/squads/useSquadFavorite';

interface SquadFavoriteButtonProps {
squad: Squad;
className?: string;
iconSize?: IconSize;
}

export const SquadFavoriteButton = ({
squad,
className,
iconSize,
}: SquadFavoriteButtonProps): ReactElement => {
const { toggleFavorite, isPending } = useSquadFavorite();
const isFavorited = !!squad.favoritedAt;

const onClick = useCallback(
(event: MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
event.stopPropagation();
toggleFavorite(squad);
},
[squad, toggleFavorite],
);

return (
<button
type="button"
aria-label={isFavorited ? 'Unfavorite squad' : 'Favorite squad'}
aria-pressed={isFavorited}
disabled={isPending}
onClick={onClick}
className={classNames(
'relative z-1 flex items-center justify-center disabled:opacity-50',
className,
)}
>
<StarIcon secondary={isFavorited} size={iconSize} />
</button>
);
};
1 change: 1 addition & 0 deletions packages/shared/src/graphql/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export interface Squad extends Source {
referralUrl?: string;
category?: SourceCategory;
moderationPostCount: number;
favoritedAt?: string | null;
}

interface SourceFlags {
Expand Down
18 changes: 18 additions & 0 deletions packages/shared/src/graphql/squads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,14 @@ export const EXPAND_PINNED_POSTS_MUTATION = gql`
}
`;

export const TOGGLE_FAVORITE_SOURCE_MUTATION = gql`
mutation ToggleFavoriteSource($sourceId: ID!) {
toggleFavoriteSource(sourceId: $sourceId) {
_
}
}
`;

export const validateSourceHandle = (handle: string, source: Source): boolean =>
source.handle === handle || source.handle === handle.toLowerCase();

Expand Down Expand Up @@ -718,6 +726,16 @@ export const expandPinnedPosts = async (
return res.expandPinnedPosts;
};

export const toggleFavoriteSource = async (
sourceId: string,
): Promise<EmptyResponse> => {
const res = await gqlClient.request(TOGGLE_FAVORITE_SOURCE_MUTATION, {
sourceId,
});

return res.toggleFavoriteSource;
};

export const verifyPermission = (
squad: Pick<Squad, 'currentMember'>,
permission: SourcePermissions,
Expand Down
35 changes: 35 additions & 0 deletions packages/shared/src/hooks/squads/useSquadFavorite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useMutation } from '@tanstack/react-query';
import type { Squad } from '../../graphql/sources';
import { toggleFavoriteSource } from '../../graphql/squads';
import { useBoot } from '../useBoot';

type UseSquadFavorite = {
toggleFavorite: (squad: Squad) => void;
isPending: boolean;
};

export const useSquadFavorite = (): UseSquadFavorite => {
const { updateSquad } = useBoot();

const { mutate, isPending } = useMutation({
mutationFn: (squad: Squad) => {
if (!squad.id) {
throw new Error('Cannot toggle favorite on squad without id');
}
return toggleFavoriteSource(squad.id);
},
onMutate: (squad) => {
const previous = squad.favoritedAt ?? null;
updateSquad({
...squad,
favoritedAt: previous ? null : new Date().toISOString(),
});
return { previous };
},
onError: (_err, squad, context) => {
updateSquad({ ...squad, favoritedAt: context?.previous ?? null });
},
});

return { toggleFavorite: mutate, isPending };
};
24 changes: 18 additions & 6 deletions packages/shared/src/hooks/useBoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,22 @@ type UseBoot = {
getPlusEntryData: () => MarketingCta | null;
};

const sortByName = (squads: Squad[]): Squad[] =>
[...squads].sort((a, b) =>
a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase() ? 1 : -1,
);
const sortSquads = (squads: Squad[]): Squad[] =>
[...squads].sort((a, b) => {
const aFav = !!a.favoritedAt;
const bFav = !!b.favoritedAt;
if (aFav !== bFav) {
return aFav ? -1 : 1;
}
if (aFav && bFav) {
const aTime = new Date(a.favoritedAt as string).getTime();
const bTime = new Date(b.favoritedAt as string).getTime();
if (aTime !== bTime) {
return bTime - aTime;
}
}
return a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase());
});

export const useBoot = (): UseBoot => {
const router = useRouter();
Expand All @@ -40,7 +52,7 @@ export const useBoot = (): UseBoot => {
return;
}

const squads = sortByName([...currentSquads, squad]);
const squads = sortSquads([...currentSquads, squad]);
client.setQueryData<Boot>(BOOT_QUERY_KEY, { ...bootData, squads });
};

Expand All @@ -57,7 +69,7 @@ export const useBoot = (): UseBoot => {
);
client.setQueryData<Boot>(BOOT_QUERY_KEY, {
...bootData,
squads: sortByName(squads ?? []),
squads: sortSquads(squads ?? []),
});
};

Expand Down
6 changes: 5 additions & 1 deletion packages/webapp/pages/squads/discover/my.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { ReactElement } from 'react';
import React, { useEffect, useMemo } from 'react';
import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext';
import { SquadList } from '@dailydotdev/shared/src/components/cards/squad/SquadList';
import { SquadFavoriteButton } from '@dailydotdev/shared/src/components/squads/SquadFavoriteButton';
import { IconSize } from '@dailydotdev/shared/src/components/Icon';
import { useRouter } from 'next/router';
import {
squadCategoriesPaths,
Expand Down Expand Up @@ -60,7 +62,9 @@ const SquadSection = ({ squads, title }: SquadSectionProps): ReactElement => {
key={squad.handle}
squad={squad}
shouldShowCount={false}
/>
>
<SquadFavoriteButton squad={squad} iconSize={IconSize.Medium} />
</SquadList>
))}
</div>
</section>
Expand Down
Loading