diff --git a/apps/portal/src/components/questions/AddToListDropdown.tsx b/apps/portal/src/components/questions/AddToListDropdown.tsx index b68ef77b..4ae9da46 100644 --- a/apps/portal/src/components/questions/AddToListDropdown.tsx +++ b/apps/portal/src/components/questions/AddToListDropdown.tsx @@ -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) => ( ); diff --git a/apps/portal/src/components/questions/ContributeQuestionCard.tsx b/apps/portal/src/components/questions/ContributeQuestionCard.tsx index f4256e8d..9313896a 100644 --- a/apps/portal/src/components/questions/ContributeQuestionCard.tsx +++ b/apps/portal/src/components/questions/ContributeQuestionCard.tsx @@ -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 (
diff --git a/apps/portal/src/components/questions/VotingButtons.tsx b/apps/portal/src/components/questions/VotingButtons.tsx index cd404dca..28824715 100644 --- a/apps/portal/src/components/questions/VotingButtons.tsx +++ b/apps/portal/src/components/questions/VotingButtons.tsx @@ -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 (
diff --git a/apps/portal/src/components/questions/card/question/BaseQuestionCard.tsx b/apps/portal/src/components/questions/card/question/BaseQuestionCard.tsx index b183b012..0c8f1460 100644 --- a/apps/portal/src/components/questions/card/question/BaseQuestionCard.tsx +++ b/apps/portal/src/components/questions/card/question/BaseQuestionCard.tsx @@ -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} /> )}
diff --git a/apps/portal/src/components/questions/protected/ProtectedContextProvider.tsx b/apps/portal/src/components/questions/protected/ProtectedContextProvider.tsx new file mode 100644 index 00000000..8dee8681 --- /dev/null +++ b/apps/portal/src/components/questions/protected/ProtectedContextProvider.tsx @@ -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({ + // eslint-disable-next-line @typescript-eslint/no-empty-function + showDialog: () => {}, +}); + +export type ProtectedContextProviderProps = PropsWithChildren< + Record +>; + +export default function ProtectedContextProvider({ + children, +}: ProtectedContextProviderProps) { + const [show, setShow] = useState(false); + + return ( + { + setShow(true); + }, + }}> + {children} + { + setShow(false); + }} + /> + + ); +} diff --git a/apps/portal/src/components/questions/protected/ProtectedDialog.tsx b/apps/portal/src/components/questions/protected/ProtectedDialog.tsx new file mode 100644 index 00000000..98e5ed62 --- /dev/null +++ b/apps/portal/src/components/questions/protected/ProtectedDialog.tsx @@ -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 ( + + } + secondaryButton={ + + ); +} diff --git a/apps/portal/src/pages/_app.tsx b/apps/portal/src/pages/_app.tsx index 9914942e..565a48f6 100644 --- a/apps/portal/src/pages/_app.tsx +++ b/apps/portal/src/pages/_app.tsx @@ -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 ( - - - + + + + + ); diff --git a/apps/portal/src/pages/questions/[questionId]/[questionSlug]/answer/[answerId]/[answerSlug]/index.tsx b/apps/portal/src/pages/questions/[questionId]/[questionSlug]/answer/[answerId]/[answerSlug]/index.tsx index 5481240a..73fd6065 100644 --- a/apps/portal/src/pages/questions/[questionId]/[questionSlug]/answer/[answerId]/[answerSlug]/index.tsx +++ b/apps/portal/src/pages/questions/[questionId]/[questionSlug]/answer/[answerId]/[answerSlug]/index.tsx @@ -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 ; diff --git a/apps/portal/src/pages/questions/[questionId]/[questionSlug]/index.tsx b/apps/portal/src/pages/questions/[questionId]/[questionSlug]/index.tsx index 2de0521c..c383a550 100644 --- a/apps/portal/src/pages/questions/[questionId]/[questionSlug]/index.tsx +++ b/apps/portal/src/pages/questions/[questionId]/[questionSlug]/index.tsx @@ -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({ 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 ; @@ -219,7 +225,7 @@ export default function QuestionPage() {
+ onSubmit={handleCommentSubmitClick(handleSubmitComment)}>