[questions][ui] create new list in dropdown

pull/514/head
Jeff Sieu 3 years ago
parent 5a281031d4
commit 158ec02c50

@ -3,22 +3,52 @@ import type { PropsWithChildren } from 'react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Fragment, useRef, useState } from 'react'; import { Fragment, useRef, useState } from 'react';
import { Menu, Transition } from '@headlessui/react'; import { Menu, Transition } from '@headlessui/react';
import { CheckIcon, HeartIcon } from '@heroicons/react/20/solid'; import { CheckIcon, HeartIcon, PlusIcon } from '@heroicons/react/20/solid';
import {
useAddQuestionToListAsync,
useCreateListAsync,
useRemoveQuestionFromListAsync,
} from '~/utils/questions/mutations';
import { useProtectedCallback } from '~/utils/questions/useProtectedCallback'; import { useProtectedCallback } from '~/utils/questions/useProtectedCallback';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
import CreateListDialog from './CreateListDialog';
export type AddToListDropdownProps = { export type AddToListDropdownProps = {
questionId: string; questionId: string;
}; };
export type DropdownButtonProps = PropsWithChildren<{
onClick: () => void;
}>;
function DropdownButton({ onClick, children }: DropdownButtonProps) {
return (
<Menu.Item>
{({ active }) => (
<button
className={clsx(
active ? 'bg-slate-100 text-slate-900' : 'text-slate-700',
'group flex w-full items-center px-4 py-2 text-sm',
)}
type="button"
onClick={onClick}>
{children}
</button>
)}
</Menu.Item>
);
}
export default function AddToListDropdown({ export default function AddToListDropdown({
questionId, questionId,
}: AddToListDropdownProps) { }: AddToListDropdownProps) {
const [menuOpened, setMenuOpened] = useState(false); const [menuOpened, setMenuOpened] = useState(false);
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [show, setShow] = useState(false);
const utils = trpc.useContext(); const createListAsync = useCreateListAsync();
const { data: lists } = trpc.useQuery(['questions.lists.getListsByUser']); const { data: lists } = trpc.useQuery(['questions.lists.getListsByUser']);
const listsWithQuestionData = useMemo(() => { const listsWithQuestionData = useMemo(() => {
@ -30,25 +60,8 @@ export default function AddToListDropdown({
})); }));
}, [lists, questionId]); }, [lists, questionId]);
const { mutateAsync: addQuestionToList } = trpc.useMutation( const addQuestionToList = useAddQuestionToListAsync();
'questions.lists.createQuestionEntry', const removeQuestionFromList = useRemoveQuestionFromListAsync();
{
// TODO: Add optimistic update
onSuccess: () => {
utils.invalidateQueries(['questions.lists.getListsByUser']);
},
},
);
const { mutateAsync: removeQuestionFromList } = trpc.useMutation(
'questions.lists.deleteQuestionEntry',
{
// TODO: Add optimistic update
onSuccess: () => {
utils.invalidateQueries(['questions.lists.getListsByUser']);
},
},
);
const addClickOutsideListener = () => { const addClickOutsideListener = () => {
document.addEventListener('click', handleClickOutside, true); document.addEventListener('click', handleClickOutside, true);
@ -101,14 +114,14 @@ export default function AddToListDropdown({
); );
return ( return (
<div>
<Menu ref={ref} as="div" className="relative inline-block text-left"> <Menu ref={ref} as="div" className="relative inline-block text-left">
<div> <div>
<Menu.Button as={CustomMenuButton}> <Menu.Button as={CustomMenuButton}>
<HeartIcon aria-hidden="true" className="-ml-1 mr-2 h-5 w-5" /> <HeartIcon aria-hidden="true" className="-ml-1 mr-2 h-5 w-5" />
Add to List Add to list
</Menu.Button> </Menu.Button>
</div> </div>
<Transition <Transition
as={Fragment} as={Fragment}
enter="transition ease-out duration-100" enter="transition ease-out duration-100"
@ -125,16 +138,7 @@ export default function AddToListDropdown({
<> <>
{(listsWithQuestionData ?? []).map((list) => ( {(listsWithQuestionData ?? []).map((list) => (
<div key={list.id} className="py-1"> <div key={list.id} className="py-1">
<Menu.Item> <DropdownButton
{({ active }) => (
<button
className={clsx(
active
? 'bg-slate-100 text-slate-900'
: 'text-slate-700',
'group flex w-full items-center px-4 py-2 text-sm',
)}
type="button"
onClick={() => { onClick={() => {
if (list.hasQuestion) { if (list.hasQuestion) {
handleDeleteFromList(list.id); handleDeleteFromList(list.id);
@ -142,22 +146,43 @@ export default function AddToListDropdown({
handleAddToList(list.id); handleAddToList(list.id);
} }
}}> }}>
<div className="flex flex-1 justify-between">
{list.name}
{list.hasQuestion && ( {list.hasQuestion && (
<CheckIcon <CheckIcon
aria-hidden="true" aria-hidden="true"
className="mr-3 h-5 w-5 text-slate-400 group-hover:text-slate-500" className="h-5 w-5 text-slate-400 group-hover:text-slate-500"
/> />
)} )}
{list.name} </div>
</button> </DropdownButton>
)}
</Menu.Item>
</div> </div>
))} ))}
<DropdownButton
onClick={() => {
setShow(true);
}}>
<PlusIcon
aria-hidden="true"
className="mr-3 h-5 w-5 text-slate-400 group-hover:text-slate-500"
/>
Create new list
</DropdownButton>
</> </>
)} )}
</Menu.Items> </Menu.Items>
</Transition> </Transition>
</Menu> </Menu>
<CreateListDialog
show={show}
onCancel={() => {
setShow(false);
}}
onSubmit={async (data) => {
await createListAsync(data);
setShow(false);
}}
/>
</div>
); );
} }

@ -15,6 +15,10 @@ import DeleteListDialog from '~/components/questions/DeleteListDialog';
import { Button } from '~/../../../packages/ui/dist'; import { Button } from '~/../../../packages/ui/dist';
import { APP_TITLE } from '~/utils/questions/constants'; import { APP_TITLE } from '~/utils/questions/constants';
import createSlug from '~/utils/questions/createSlug'; import createSlug from '~/utils/questions/createSlug';
import {
useCreateListAsync,
useDeleteListAsync,
} from '~/utils/questions/mutations';
import relabelQuestionAggregates from '~/utils/questions/relabelQuestionAggregates'; import relabelQuestionAggregates from '~/utils/questions/relabelQuestionAggregates';
import { useProtectedCallback } from '~/utils/questions/useProtectedCallback'; import { useProtectedCallback } from '~/utils/questions/useProtectedCallback';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
@ -22,24 +26,10 @@ import { trpc } from '~/utils/trpc';
export default function ListPage() { export default function ListPage() {
const utils = trpc.useContext(); const utils = trpc.useContext();
const { data: lists } = trpc.useQuery(['questions.lists.getListsByUser']); const { data: lists } = trpc.useQuery(['questions.lists.getListsByUser']);
const { mutateAsync: createList } = trpc.useMutation(
'questions.lists.create', const createListAsync = useCreateListAsync();
{ const deleteListAsync = useDeleteListAsync();
onSuccess: () => {
// TODO: Add optimistic update
utils.invalidateQueries(['questions.lists.getListsByUser']);
},
},
);
const { mutateAsync: deleteList } = trpc.useMutation(
'questions.lists.delete',
{
onSuccess: () => {
// TODO: Add optimistic update
utils.invalidateQueries(['questions.lists.getListsByUser']);
},
},
);
const { mutateAsync: deleteQuestionEntry } = trpc.useMutation( const { mutateAsync: deleteQuestionEntry } = trpc.useMutation(
'questions.lists.deleteQuestionEntry', 'questions.lists.deleteQuestionEntry',
{ {
@ -57,7 +47,7 @@ export default function ListPage() {
const [listIdToDelete, setListIdToDelete] = useState(''); const [listIdToDelete, setListIdToDelete] = useState('');
const handleDeleteList = async (listId: string) => { const handleDeleteList = async (listId: string) => {
await deleteList({ await deleteListAsync({
id: listId, id: listId,
}); });
setShowDeleteListDialog(false); setShowDeleteListDialog(false);
@ -68,7 +58,7 @@ export default function ListPage() {
}; };
const handleCreateList = async (data: CreateListFormData) => { const handleCreateList = async (data: CreateListFormData) => {
await createList({ await createListAsync({
name: data.name, name: data.name,
}); });
setShowCreateListDialog(false); setShowCreateListDialog(false);

@ -0,0 +1,60 @@
import { trpc } from '../trpc';
export function useAddQuestionToListAsync() {
const utils = trpc.useContext();
const { mutateAsync: addQuestionToListAsync } = trpc.useMutation(
'questions.lists.createQuestionEntry',
{
// TODO: Add optimistic update
onSuccess: () => {
utils.invalidateQueries(['questions.lists.getListsByUser']);
},
},
);
return addQuestionToListAsync;
}
export function useRemoveQuestionFromListAsync() {
const utils = trpc.useContext();
const { mutateAsync: removeQuestionFromListAsync } = trpc.useMutation(
'questions.lists.deleteQuestionEntry',
{
// TODO: Add optimistic update
onSuccess: () => {
utils.invalidateQueries(['questions.lists.getListsByUser']);
},
},
);
return removeQuestionFromListAsync;
}
export function useCreateListAsync() {
const utils = trpc.useContext();
const { mutateAsync: createListAsync } = trpc.useMutation(
'questions.lists.create',
{
onSuccess: () => {
// TODO: Add optimistic update
utils.invalidateQueries(['questions.lists.getListsByUser']);
},
},
);
return createListAsync;
}
export function useDeleteListAsync() {
const utils = trpc.useContext();
const { mutateAsync: deleteListAsync } = trpc.useMutation(
'questions.lists.delete',
{
onSuccess: () => {
// TODO: Add optimistic update
utils.invalidateQueries(['questions.lists.getListsByUser']);
},
},
);
return deleteListAsync;
}
Loading…
Cancel
Save