import { type ReactNode, useState } from 'react';
import type { AppBskyActorDefs } from '@atcute/bluesky';
import type { Did } from '@atcute/lexicons';
import { useProfileShadow } from '#/state/cache/profile-shadow';
import { useLeaveConvo } from '#/state/queries/messages/leave-conversation';
import { useProfileBlockMutationQueue, useProfileQuery } from '#/state/queries/profile';
import { CenteredSpinner } from '#/components/CenteredSpinner';
import * as Dialog from '#/components/Dialog';
import * as Toggle from '#/components/forms/Toggle';
import { Stack } from '#/components/Stack';
import { Text } from '#/components/Text';
import * as Toast from '#/components/Toast';
import { Button, ButtonText } from '#/components/web/Button';
import { m } from '#/paraglide/messages';
import { useRouter } from '#/router';
type ReportDialogParams = {
convoId: string;
did: Did;
};
/** The follow-up actions offered after a report; each maps to a checkbox in {@link DoneStep}. */
type ReportAction = 'block' | 'leave';
// Toggle.Group hands back the raw checkbox names, so narrow them before they reach `actions`.
const isReportAction = (value: string): value is ReportAction => value === 'block' || value === 'leave';
/**
* Dialog shown after a report is submitted, allowing the user to block the reporter and/or leave the
* conversation.
*/
export function AfterReportDialog({
handle,
params,
currentScreen,
onClose,
}: {
handle: Dialog.DialogHandle;
params: ReportDialogParams;
currentScreen: 'list' | 'conversation';
onClose?: () => void;
}): ReactNode {
return (
<Dialog.Root
handle={handle}
onOpenChange={(open) => {
if (!open) {
onClose?.();
}
}}
>
<Dialog.Popup label={m['components.dms.block.orDelete.prompt']()} size="narrow">
<DialogInner handle={handle} params={params} currentScreen={currentScreen} />
</Dialog.Popup>
</Dialog.Root>
);
}
function DialogInner({
handle,
params,
currentScreen,
}: {
handle: Dialog.DialogHandle;
params: ReportDialogParams;
currentScreen: 'list' | 'conversation';
}) {
const { data: profile, isPending, isError } = useProfileQuery({ did: params.did });
if (isPending) {
return <CenteredSpinner label={m['common.status.loading']()} size="xl" />;
}
if (isError || !profile) {
return (
<Stack gap="lg">
<Stack gap="xs">
<Dialog.TitleRow>
<Dialog.Title>{m['components.dms.report.submitted']()}</Dialog.Title>
<Dialog.Close />
</Dialog.TitleRow>
<Text color="textContrastMedium" size="md">
{m['components.dms.report.received']()}
</Text>
</Stack>
<Dialog.Actions>
<Button
color="secondary"
label={m['common.action.close']()}
onClick={() => handle.close()}
size="large"
variant="solid"
>
<ButtonText>{m['common.action.close']()}</ButtonText>
</Button>
</Dialog.Actions>
</Stack>
);
}
return (
<DoneStep convoId={params.convoId} currentScreen={currentScreen} handle={handle} profile={profile} />
);
}
function DoneStep({
convoId,
currentScreen,
handle,
profile,
}: {
convoId: string;
currentScreen: 'list' | 'conversation';
handle: Dialog.DialogHandle;
profile: AppBskyActorDefs.ProfileViewDetailed;
}) {
const router = useRouter();
const [actions, setActions] = useState<ReportAction[]>(['block', 'leave']);
const shadow = useProfileShadow(profile);
const [queueBlock] = useProfileBlockMutationQueue(shadow);
const { mutate: leaveConvo } = useLeaveConvo(convoId, {
onMutate: () => {
if (currentScreen === 'conversation') {
router.navigate({ replace: true, to: { name: 'Messages' } });
}
},
onError: () => {
Toast.show(m['components.dms.leave.error.leave'](), {
type: 'error',
});
},
});
let btnText = m['common.action.done']();
let toastMsg: string | undefined;
if (actions.includes('leave') && actions.includes('block')) {
btnText = m['components.dms.block.orDelete.confirm']();
toastMsg = m['components.dms.delete.conversationDeleted']();
} else if (actions.includes('leave')) {
btnText = m['common.chat.action.deleteConversation']();
toastMsg = m['components.dms.delete.conversationDeleted']();
} else if (actions.includes('block')) {
btnText = m['components.dms.block.title']();
toastMsg = m['components.dms.block.userBlocked']();
}
const onPressPrimaryAction = () => {
// close first: leaving the convo navigates away from the screen hosting this dialog
handle.close();
if (actions.includes('block')) {
void queueBlock();
}
if (actions.includes('leave')) {
leaveConvo();
}
if (toastMsg) {
Toast.show(toastMsg, {
type: 'success',
});
}
};
return (
<Stack gap="_2xl">
<Stack gap="xs">
<Dialog.TitleRow>
<Dialog.Title>{m['components.dms.report.submitted']()}</Dialog.Title>
<Dialog.Close />
</Dialog.TitleRow>
<Text color="textContrastMedium" size="md">
{m['components.dms.report.received']()}
</Text>
</Stack>
<Toggle.Group
label={m['components.dms.block.orDelete.label']()}
onChange={(values) => setActions(values.filter(isReportAction))}
values={actions}
>
<Toggle.PanelGroup>
<Toggle.Item label={m['components.dms.block.action.block']()} name="block">
<Toggle.Panel adjacent="trailing">
<Toggle.CheckboxIndicator />
<Toggle.PanelText>{m['components.dms.block.action.block']()}</Toggle.PanelText>
</Toggle.Panel>
</Toggle.Item>
<Toggle.Item label={m['common.chat.action.deleteConversation']()} name="leave">
<Toggle.Panel adjacent="leading">
<Toggle.CheckboxIndicator />
<Toggle.PanelText>{m['common.chat.action.deleteConversation']()}</Toggle.PanelText>
</Toggle.Panel>
</Toggle.Item>
</Toggle.PanelGroup>
</Toggle.Group>
<Dialog.Actions direction="column" reverse>
<Button
color="secondary"
label={m['common.action.close']()}
onClick={() => handle.close()}
size="large"
variant="solid"
>
<ButtonText>{m['common.action.close']()}</ButtonText>
</Button>
<Button
color={actions.length > 0 ? 'negative' : 'primary'}
label={btnText}
onClick={onPressPrimaryAction}
size="large"
variant="solid"
>
<ButtonText>{btnText}</ButtonText>
</Button>
</Dialog.Actions>
</Stack>
);
}