Skip to content
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ReactElement } from 'react';
import React from 'react';
import classNames from 'classnames';
import type { UserHotTake } from '../../../../graphql/user/userHotTake';
import type { HotTake } from '../../../../graphql/user/userHotTake';
import {
Typography,
TypographyType,
Expand All @@ -11,23 +11,31 @@ import {
Button,
ButtonSize,
ButtonVariant,
ButtonColor,
} from '../../../../components/buttons/Button';
import { EditIcon, TrashIcon } from '../../../../components/icons';
import { EditIcon, TrashIcon, UpvoteIcon } from '../../../../components/icons';
import InteractionCounter from '../../../../components/InteractionCounter';
import { IconSize } from '../../../../components/Icon';
import { QuaternaryButton } from '../../../../components/buttons/QuaternaryButton';
import { Tooltip } from '../../../../components/tooltip/Tooltip';

interface HotTakeItemProps {
item: UserHotTake;
item: HotTake;
isOwner: boolean;
onEdit?: (item: UserHotTake) => void;
onDelete?: (item: UserHotTake) => void;
onEdit?: (item: HotTake) => void;
onDelete?: (item: HotTake) => void;
onUpvoteClick?: (item: HotTake) => void;
}

export function HotTakeItem({
item,
isOwner,
onEdit,
onDelete,
onUpvoteClick,
}: HotTakeItemProps): ReactElement {
const { emoji, title, subtitle } = item;
const isUpvoteActive = item.upvoted;

return (
<div
Expand All @@ -47,7 +55,7 @@ export function HotTakeItem({
color={TypographyColor.Primary}
bold
>
&ldquo;{title}&rdquo;
{title}
</Typography>
{subtitle && (
<Typography
Expand All @@ -58,28 +66,59 @@ export function HotTakeItem({
</Typography>
)}
</div>
{isOwner && (
<div className="flex gap-1 opacity-0 transition-opacity group-hover:opacity-100">
{onEdit && (
<Button
variant={ButtonVariant.Tertiary}
size={ButtonSize.XSmall}
icon={<EditIcon />}
onClick={() => onEdit(item)}
aria-label="Edit hot take"
/>
)}
{onDelete && (
<Button
<div className="flex items-center gap-1">
{isOwner && (
<div className="flex gap-1 opacity-0 transition-opacity group-hover:opacity-100">
{onEdit && (
<Button
variant={ButtonVariant.Tertiary}
size={ButtonSize.XSmall}
icon={<EditIcon />}
onClick={() => onEdit(item)}
aria-label="Edit hot take"
/>
)}
{onDelete && (
<Button
variant={ButtonVariant.Tertiary}
size={ButtonSize.XSmall}
icon={<TrashIcon />}
onClick={() => onDelete(item)}
aria-label="Delete hot take"
/>
)}
</div>
)}
{onUpvoteClick && (
<Tooltip
content={isUpvoteActive ? 'Remove upvote' : 'Upvote'}
side="bottom"
>
<QuaternaryButton
labelClassName="!pl-[1px]"
className="btn-tertiary-avocado"
color={ButtonColor.Avocado}
pressed={isUpvoteActive}
onClick={() => onUpvoteClick(item)}
variant={ButtonVariant.Tertiary}
size={ButtonSize.XSmall}
icon={<TrashIcon />}
onClick={() => onDelete(item)}
aria-label="Delete hot take"
/>
)}
</div>
)}
icon={
<UpvoteIcon secondary={isUpvoteActive} size={IconSize.XSmall} />
}
>
{item.upvotes > 0 && (
<InteractionCounter
className={classNames(
'tabular-nums typo-footnote',
!item.upvotes && 'invisible',
)}
value={item.upvotes}
/>
)}
</QuaternaryButton>
</Tooltip>
)}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { Button, ButtonVariant } from '../../../../components/buttons/Button';
import { ModalHeader } from '../../../../components/modals/common/ModalHeader';
import { useViewSize, ViewSize } from '../../../../hooks';
import type {
UserHotTake,
AddUserHotTakeInput,
HotTake,
AddHotTakeInput,
} from '../../../../graphql/user/userHotTake';

const EmojiPicker = dynamic(
Expand All @@ -32,8 +32,8 @@ const hotTakeFormSchema = z.object({
type HotTakeFormData = z.infer<typeof hotTakeFormSchema>;

type HotTakeModalProps = Omit<ModalProps, 'children'> & {
onSubmit: (input: AddUserHotTakeInput) => Promise<void>;
existingItem?: UserHotTake;
onSubmit: (input: AddHotTakeInput) => Promise<void>;
existingItem?: HotTake;
};

export function HotTakeModal({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ReactElement } from 'react';
import React, { useState, useCallback } from 'react';
import type { PublicProfile } from '../../../../lib/user';
import { useUserHotTakes, MAX_HOT_TAKES } from '../../hooks/useUserHotTakes';
import { useHotTakes, MAX_HOT_TAKES } from '../../hooks/useHotTakes';
import {
Typography,
TypographyType,
Expand All @@ -16,11 +16,13 @@ import { PlusIcon } from '../../../../components/icons';
import { HotTakeItem } from './HotTakeItem';
import { HotTakeModal } from './HotTakeModal';
import type {
UserHotTake,
AddUserHotTakeInput,
HotTake,
AddHotTakeInput,
} from '../../../../graphql/user/userHotTake';
import { useToastNotification } from '../../../../hooks/useToastNotification';
import { usePrompt } from '../../../../hooks/usePrompt';
import { useVoteHotTake } from '../../../../hooks/vote/useVoteHotTake';
import { Origin } from '../../../../lib/log';

interface ProfileUserHotTakesProps {
user: PublicProfile;
Expand All @@ -30,15 +32,16 @@ export function ProfileUserHotTakes({
user,
}: ProfileUserHotTakesProps): ReactElement | null {
const { hotTakes, isOwner, canAddMore, add, update, remove } =
useUserHotTakes(user);
useHotTakes(user);
const { displayToast } = useToastNotification();
const { showPrompt } = usePrompt();
const { toggleUpvote } = useVoteHotTake();

const [isModalOpen, setIsModalOpen] = useState(false);
const [editingItem, setEditingItem] = useState<UserHotTake | null>(null);
const [editingItem, setEditingItem] = useState<HotTake | null>(null);

const handleAdd = useCallback(
async (input: AddUserHotTakeInput) => {
async (input: AddHotTakeInput) => {
try {
await add(input);
displayToast('Hot take added');
Expand All @@ -50,13 +53,13 @@ export function ProfileUserHotTakes({
[add, displayToast],
);

const handleEdit = useCallback((item: UserHotTake) => {
const handleEdit = useCallback((item: HotTake) => {
setEditingItem(item);
setIsModalOpen(true);
}, []);

const handleUpdate = useCallback(
async (input: AddUserHotTakeInput) => {
async (input: AddHotTakeInput) => {
if (!editingItem) {
return;
}
Expand All @@ -79,7 +82,7 @@ export function ProfileUserHotTakes({
);

const handleDelete = useCallback(
async (item: UserHotTake) => {
async (item: HotTake) => {
const confirmed = await showPrompt({
title: 'Remove hot take?',
description: `Are you sure you want to remove "${item.title}"?`,
Expand Down Expand Up @@ -112,6 +115,13 @@ export function ProfileUserHotTakes({
setIsModalOpen(true);
}, [canAddMore, displayToast]);

const handleUpvote = useCallback(
async (item: HotTake) => {
await toggleUpvote({ payload: item, origin: Origin.HotTakeList });
},
[toggleUpvote],
);

const hasItems = hotTakes.length > 0;

if (!hasItems && !isOwner) {
Expand Down Expand Up @@ -149,6 +159,7 @@ export function ProfileUserHotTakes({
isOwner={isOwner}
onEdit={handleEdit}
onDelete={handleDelete}
onUpvoteClick={handleUpvote}
/>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useMemo, useCallback } from 'react';
import type { PublicProfile } from '../../../lib/user';
import type {
AddUserHotTakeInput,
UpdateUserHotTakeInput,
ReorderUserHotTakeInput,
AddHotTakeInput,
UpdateHotTakeInput,
ReorderHotTakeInput,
} from '../../../graphql/user/userHotTake';
import {
getUserHotTakes,
addUserHotTake,
updateUserHotTake,
deleteUserHotTake,
reorderUserHotTakes,
getHotTakes,
addHotTake,
updateHotTake,
deleteHotTake,
reorderHotTakes,
} from '../../../graphql/user/userHotTake';
import { generateQueryKey, RequestKey, StaleTime } from '../../../lib/query';
import { useAuthContext } from '../../../contexts/AuthContext';

export const MAX_HOT_TAKES = 5;

export function useUserHotTakes(user: PublicProfile | null) {
export const useHotTakes = (user: PublicProfile | null) => {
const queryClient = useQueryClient();
const { user: loggedUser } = useAuthContext();
const isOwner = loggedUser?.id === user?.id;
Expand All @@ -27,7 +27,7 @@ export function useUserHotTakes(user: PublicProfile | null) {

const query = useQuery({
queryKey,
queryFn: () => getUserHotTakes(user?.id as string),
queryFn: () => getHotTakes(user?.id as string),
staleTime: StaleTime.Default,
enabled: !!user?.id,
});
Expand All @@ -44,29 +44,23 @@ export function useUserHotTakes(user: PublicProfile | null) {
}, [queryClient, queryKey]);

const addMutation = useMutation({
mutationFn: (input: AddUserHotTakeInput) => addUserHotTake(input),
mutationFn: (input: AddHotTakeInput) => addHotTake(input),
onSuccess: invalidateQuery,
});

const updateMutation = useMutation({
mutationFn: ({
id,
input,
}: {
id: string;
input: UpdateUserHotTakeInput;
}) => updateUserHotTake(id, input),
mutationFn: ({ id, input }: { id: string; input: UpdateHotTakeInput }) =>
updateHotTake(id, input),
onSuccess: invalidateQuery,
});

const deleteMutation = useMutation({
mutationFn: (id: string) => deleteUserHotTake(id),
mutationFn: (id: string) => deleteHotTake(id),
onSuccess: invalidateQuery,
});

const reorderMutation = useMutation({
mutationFn: (items: ReorderUserHotTakeInput[]) =>
reorderUserHotTakes(items),
mutationFn: (items: ReorderHotTakeInput[]) => reorderHotTakes(items),
onSuccess: invalidateQuery,
});

Expand All @@ -85,4 +79,4 @@ export function useUserHotTakes(user: PublicProfile | null) {
isDeleting: deleteMutation.isPending,
isReordering: reorderMutation.isPending,
};
}
};
Loading