mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 16:28:12 +00:00
106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import { Button, Modal, Radio, Stack, Text } from "@mantine/core";
|
|
import { useMutation } from "@tanstack/react-query";
|
|
import { KeyRound } from "lucide-react";
|
|
import { useState } from "react";
|
|
|
|
import { useAuth } from "@/auth/useAuth";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
|
|
import { api } from "@/services/api";
|
|
import type { Company, ResetChannel } from "@/types/customer";
|
|
|
|
export interface ResetPasswordActionProps {
|
|
company: Pick<Company, "id" | "email" | "phone">;
|
|
}
|
|
|
|
/**
|
|
* Staff-triggered password reset. Sends a one-time code to the customer's
|
|
* primary contact; the customer picks their own new password. No credential is
|
|
* ever shown to or handled by staff.
|
|
*/
|
|
export default function ResetPasswordAction({ company }: ResetPasswordActionProps) {
|
|
const { user } = useAuth();
|
|
const { toast } = useToast();
|
|
const [opened, setOpened] = useState(false);
|
|
const [channel, setChannel] = useState<ResetChannel>("phone");
|
|
|
|
const { mutate, isPending } = useMutation(
|
|
api.customers.resetPassword.mutationOptions({
|
|
onSuccess: (result) => {
|
|
setOpened(false);
|
|
toast({
|
|
title: "Reset code sent",
|
|
description: `The customer can now reset their password using the code sent to ${result.maskedTarget}.`,
|
|
});
|
|
},
|
|
onError: (error) => {
|
|
toast({
|
|
title: "Could not send reset code",
|
|
description: error.message,
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
}),
|
|
);
|
|
|
|
if (!hasPermission(user, FREIGHT_PERMS.customers.resetPassword)) return null;
|
|
|
|
return (
|
|
<>
|
|
<Button
|
|
variant="default"
|
|
leftSection={<KeyRound size={16} />}
|
|
onClick={() => setOpened(true)}
|
|
>
|
|
Reset password
|
|
</Button>
|
|
|
|
<Modal
|
|
opened={opened}
|
|
onClose={() => setOpened(false)}
|
|
title="Send a password-reset code"
|
|
centered
|
|
>
|
|
<Stack gap="md">
|
|
<Text size="sm" c="dimmed">
|
|
We'll send a one-time code to this customer's primary contact.
|
|
They choose their own new password — you will not see it.
|
|
</Text>
|
|
|
|
<Radio.Group
|
|
value={channel}
|
|
onChange={(v) => setChannel(v as ResetChannel)}
|
|
label="Send the code via"
|
|
>
|
|
<Stack gap="xs" mt="xs">
|
|
<Radio
|
|
value="phone"
|
|
label="SMS"
|
|
description={company.phone ?? "No phone on the company record"}
|
|
/>
|
|
<Radio
|
|
value="email"
|
|
label="Email"
|
|
description={company.email ?? "No email on the company record"}
|
|
/>
|
|
</Stack>
|
|
</Radio.Group>
|
|
|
|
<Text size="xs" c="dimmed">
|
|
The code goes to the primary contact's own email or phone, which
|
|
may differ from the company contact details shown above.
|
|
</Text>
|
|
|
|
<Button
|
|
color="edr-green"
|
|
loading={isPending}
|
|
onClick={() => mutate({ companyId: company.id, channel })}
|
|
>
|
|
Send reset code
|
|
</Button>
|
|
</Stack>
|
|
</Modal>
|
|
</>
|
|
);
|
|
}
|