[questions][feat] add useProtectedCallback hook (#472)

pull/475/head
Jeff Sieu 2 years ago committed by GitHub
parent ade6d1d88d
commit 538fa5ccf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,6 +5,7 @@ import { Fragment, useRef, useState } from 'react';
import { Menu, Transition } from '@headlessui/react';
import { CheckIcon, HeartIcon } from '@heroicons/react/20/solid';
import { useProtectedCallback } from '~/utils/questions/useProtectedCallback';
import { trpc } from '~/utils/trpc';
export type AddToListDropdownProps = {
@ -85,14 +86,16 @@ export default function AddToListDropdown({
});
};
const handleMenuButtonClick = useProtectedCallback(() => {
addClickOutsideListener();
setMenuOpened(!menuOpened);
});
const CustomMenuButton = ({ children }: PropsWithChildren<unknown>) => (
<button
className="focus:ring-primary-500 inline-flex w-full justify-center rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-slate-100"
type="button"
onClick={() => {
addClickOutsideListener();
setMenuOpened(!menuOpened);
}}>
onClick={handleMenuButtonClick}>
{children}
</button>
);

@ -6,6 +6,8 @@ import {
} from '@heroicons/react/24/outline';
import { TextInput } from '@tih/ui';
import { useProtectedCallback } from '~/utils/questions/useProtectedCallback';
import ContributeQuestionDialog from './ContributeQuestionDialog';
import type { ContributeQuestionFormProps } from './forms/ContributeQuestionForm';
@ -23,9 +25,9 @@ export default function ContributeQuestionCard({
setShowDraftDialog(false);
};
const handleOpenContribute = () => {
const handleOpenContribute = useProtectedCallback(() => {
setShowDraftDialog(true);
};
});
return (
<div className="w-full">

@ -4,6 +4,8 @@ import type { Vote } from '@prisma/client';
import type { ButtonSize } from '@tih/ui';
import { Button } from '@tih/ui';
import { useProtectedCallback } from '~/utils/questions/useProtectedCallback';
export type BackendVote = {
id: string;
vote: Vote;
@ -31,6 +33,15 @@ export default function VotingButtons({
vote?.vote === 'UPVOTE' ? 'secondary' : 'tertiary';
const downvoteButtonVariant =
vote?.vote === 'DOWNVOTE' ? 'secondary' : 'tertiary';
const handleUpvoteClick = useProtectedCallback(() => {
onUpvote();
});
const handleDownvoteClick = useProtectedCallback(() => {
onDownvote();
});
return (
<div className="flex flex-col items-center">
<Button
@ -42,7 +53,7 @@ export default function VotingButtons({
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
onUpvote();
handleUpvoteClick();
}}
/>
<p>{upvoteCount}</p>
@ -55,7 +66,7 @@ export default function VotingButtons({
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
onDownvote();
handleDownvoteClick();
}}
/>
</div>

@ -9,6 +9,7 @@ import {
import type { QuestionsQuestionType } from '@prisma/client';
import { Button } from '@tih/ui';
import { useProtectedCallback } from '~/utils/questions/useProtectedCallback';
import { useQuestionVote } from '~/utils/questions/useVote';
import AddToListDropdown from '../../AddToListDropdown';
@ -168,6 +169,10 @@ export default function BaseQuestionCard({
return countryCount;
}, [countries]);
const handleCreateEncounterClick = useProtectedCallback(() => {
setShowReceivedForm(true);
});
const cardContent = (
<>
{showVoteButtons && (
@ -244,10 +249,7 @@ export default function BaseQuestionCard({
label={createEncounterButtonText}
size="sm"
variant="tertiary"
onClick={(event) => {
event.preventDefault();
setShowReceivedForm(true);
}}
onClick={handleCreateEncounterClick}
/>
)}
</div>

@ -0,0 +1,40 @@
import type { PropsWithChildren } from 'react';
import { createContext, useState } from 'react';
import ProtectedDialog from './ProtectedDialog';
export type ProtectedContextData = {
showDialog: () => void;
};
export const ProtectedContext = createContext<ProtectedContextData>({
// eslint-disable-next-line @typescript-eslint/no-empty-function
showDialog: () => {},
});
export type ProtectedContextProviderProps = PropsWithChildren<
Record<string, unknown>
>;
export default function ProtectedContextProvider({
children,
}: ProtectedContextProviderProps) {
const [show, setShow] = useState(false);
return (
<ProtectedContext.Provider
value={{
showDialog: () => {
setShow(true);
},
}}>
{children}
<ProtectedDialog
show={show}
onClose={() => {
setShow(false);
}}
/>
</ProtectedContext.Provider>
);
}

@ -0,0 +1,36 @@
import { signIn } from 'next-auth/react';
import { Button, Dialog } from '@tih/ui';
export type ProtectedDialogProps = {
onClose: () => void;
show: boolean;
};
export default function ProtectedDialog({
show,
onClose,
}: ProtectedDialogProps) {
const handlePrimaryClick = () => {
signIn();
onClose();
};
return (
<Dialog
isShown={show}
primaryButton={
<Button
label="Sign in"
variant="primary"
onClick={handlePrimaryClick}
/>
}
secondaryButton={
<Button label="Cancel" variant="tertiary" onClick={onClose} />
}
title="Sign in to continue"
onClose={onClose}>
<p>This action requires you to be signed in.</p>
</Dialog>
);
}

@ -9,6 +9,7 @@ import { loggerLink } from '@trpc/client/links/loggerLink';
import { withTRPC } from '@trpc/next';
import AppShell from '~/components/global/AppShell';
import ProtectedContextProvider from '~/components/questions/protected/ProtectedContextProvider';
import type { AppRouter } from '~/server/router';
@ -21,9 +22,11 @@ const MyApp: AppType<{ session: Session | null }> = ({
return (
<SessionProvider session={session}>
<ToastsProvider>
<AppShell>
<Component {...pageProps} />
</AppShell>
<ProtectedContextProvider>
<AppShell>
<Component {...pageProps} />
</AppShell>
</ProtectedContextProvider>
</ToastsProvider>
</SessionProvider>
);

@ -13,6 +13,7 @@ import SortOptionsSelect from '~/components/questions/SortOptionsSelect';
import { APP_TITLE } from '~/utils/questions/constants';
import { useFormRegister } from '~/utils/questions/useFormRegister';
import { useProtectedCallback } from '~/utils/questions/useProtectedCallback';
import { trpc } from '~/utils/trpc';
import { SortOrder, SortType } from '~/types/questions.d';
@ -82,13 +83,15 @@ export default function QuestionPage() {
},
);
const handleSubmitComment = (data: AnswerCommentData) => {
resetComment();
addComment({
answerId: answerId as string,
content: data.commentContent,
});
};
const handleSubmitComment = useProtectedCallback(
(data: AnswerCommentData) => {
resetComment();
addComment({
answerId: answerId as string,
content: data.commentContent,
});
},
);
if (!answer) {
return <FullScreenSpinner />;

@ -16,6 +16,7 @@ import { APP_TITLE } from '~/utils/questions/constants';
import createSlug from '~/utils/questions/createSlug';
import relabelQuestionAggregates from '~/utils/questions/relabelQuestionAggregates';
import { useFormRegister } from '~/utils/questions/useFormRegister';
import { useProtectedCallback } from '~/utils/questions/useProtectedCallback';
import { trpc } from '~/utils/trpc';
import { SortOrder, SortType } from '~/types/questions.d';
@ -53,10 +54,11 @@ export default function QuestionPage() {
const {
register: comRegister,
handleSubmit: handleCommentSubmit,
handleSubmit: handleCommentSubmitClick,
reset: resetComment,
formState: { isDirty: isCommentDirty, isValid: isCommentValid },
} = useForm<QuestionCommentData>({ mode: 'onChange' });
const commentRegister = useFormRegister(comRegister);
const { questionId } = router.query;
@ -149,21 +151,25 @@ export default function QuestionPage() {
},
);
const handleSubmitAnswer = (data: AnswerQuestionData) => {
addAnswer({
content: data.answerContent,
questionId: questionId as string,
});
resetAnswer();
};
const handleSubmitAnswer = useProtectedCallback(
(data: AnswerQuestionData) => {
addAnswer({
content: data.answerContent,
questionId: questionId as string,
});
resetAnswer();
},
);
const handleSubmitComment = (data: QuestionCommentData) => {
addComment({
content: data.commentContent,
questionId: questionId as string,
});
resetComment();
};
const handleSubmitComment = useProtectedCallback(
(data: QuestionCommentData) => {
addComment({
content: data.commentContent,
questionId: questionId as string,
});
resetComment();
},
);
if (!question) {
return <FullScreenSpinner />;
@ -219,7 +225,7 @@ export default function QuestionPage() {
<div className="mt-4 px-4">
<form
className="mb-2"
onSubmit={handleCommentSubmit(handleSubmitComment)}>
onSubmit={handleCommentSubmitClick(handleSubmitComment)}>
<TextArea
{...commentRegister('commentContent', {
minLength: 1,

@ -16,6 +16,7 @@ import { Button } from '~/../../../packages/ui/dist';
import { APP_TITLE } from '~/utils/questions/constants';
import createSlug from '~/utils/questions/createSlug';
import relabelQuestionAggregates from '~/utils/questions/relabelQuestionAggregates';
import { useProtectedCallback } from '~/utils/questions/useProtectedCallback';
import { trpc } from '~/utils/trpc';
export default function ListPage() {
@ -77,6 +78,10 @@ export default function ListPage() {
setShowCreateListDialog(false);
};
const handleAddClick = useProtectedCallback(() => {
setShowCreateListDialog(true);
});
const listOptions = (
<>
<ul className="flex flex-1 flex-col divide-y divide-solid divide-slate-200">
@ -157,10 +162,10 @@ export default function ListPage() {
label="Create"
size="md"
variant="tertiary"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setShowCreateListDialog(true);
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleAddClick();
}}
/>
</div>
@ -223,11 +228,13 @@ export default function ListPage() {
onCancel={handleDeleteListCancel}
onDelete={() => {
handleDeleteList(listIdToDelete);
}}></DeleteListDialog>
}}
/>
<CreateListDialog
show={showCreateListDialog}
onCancel={handleCreateListCancel}
onSubmit={handleCreateList}></CreateListDialog>
onSubmit={handleCreateList}
/>
</section>
</div>
</main>

@ -0,0 +1,22 @@
import { useSession } from 'next-auth/react';
import { useCallback, useContext } from 'react';
import { ProtectedContext } from '~/components/questions/protected/ProtectedContextProvider';
export const useProtectedCallback = <T extends Array<unknown>, U>(
callback: (...args: T) => U,
) => {
const { showDialog } = useContext(ProtectedContext);
const { status } = useSession();
const protectedCallback = useCallback(
(...args: T) => {
if (status === 'authenticated') {
return callback(...args);
}
showDialog();
},
[callback, showDialog, status],
);
return protectedCallback;
};
Loading…
Cancel
Save