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 AfterReportConversationDialog({
handle,
params,
currentScreen,
}: {
handle: Dialog.DialogHandle;
params: ReportDialogParams;
currentScreen: 'list' | 'conversation';
}): ReactNode {
return (
<Dialog.Root handle={handle}>
<Dialog.Popup label={m['components.dms.block.orLeave.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);
// blocking implies leaving, so `block` drags `leave` along with it; `leave` still toggles on its own.
const handleActionsChange = (values: string[]) => {
const newActions = values.filter(isReportAction);
const hadBlock = actions.includes('block');
const hasBlock = newActions.includes('block');
if (!hadBlock && hasBlock) {
setActions(newActions.includes('leave') ? newActions : [...newActions, 'leave']);
} else if (hadBlock && !hasBlock) {
setActions(newActions.filter((action) => action !== 'leave'));
} else {
setActions(newActions);
}
};
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.orLeave.confirm']();
toastMsg = m['components.dms.leave.conversationLeft']();
} else if (actions.includes('leave')) {
btnText = m['components.dms.leave.action.conversation']();
toastMsg = m['components.dms.leave.conversationLeft']();
} else if (actions.includes('block')) {
// shouldn't be able to reach this, but here for completeness
btnText = m['components.dms.block.action.block']();
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.orLeave.label']()}
onChange={handleActionsChange}
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
disabled={actions.includes('block')}
label={m['components.dms.leave.action.conversation']()}
name="leave"
>
<Toggle.Panel adjacent="leading">
<Toggle.CheckboxIndicator />
<Toggle.PanelText>{m['components.dms.leave.action.conversation']()}</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>
);
}