Ship the full Communication portal (real SMS API, feature-locked future channels), BFF proxy, docs, snapshot, and phase handover. Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
4.7 KiB
TypeScript
108 lines
4.7 KiB
TypeScript
"use client";
|
||
|
||
import { useForm } from "react-hook-form";
|
||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||
import { toast } from "sonner";
|
||
import { communicationApi } from "@/modules/communication/services/communication-api";
|
||
import { useTenantId } from "@/hooks/useTenantId";
|
||
import { CommunicationPageLoader, CommunicationPageError } from "@/modules/communication/pages/shared";
|
||
import { PageHeader, Card, CardContent, Button, FormField, Input, Textarea, Select } from "@/components/ds";
|
||
import { CommunicationBreadcrumbs } from "@/modules/communication/components/CommunicationBreadcrumbs";
|
||
import { useCommunicationPermissions } from "@/modules/communication/hooks/useCommunicationPermissions";
|
||
import { PermissionDeniedState } from "@/src/shared/ui";
|
||
|
||
export function SendMessagePage() {
|
||
const { tenantId } = useTenantId();
|
||
const perms = useCommunicationPermissions();
|
||
const form = useForm({
|
||
defaultValues: {
|
||
channel: "sms",
|
||
to_address: "",
|
||
body: "",
|
||
template_key: "",
|
||
priority: "normal",
|
||
correlation_id: "",
|
||
scheduled_at: "",
|
||
},
|
||
});
|
||
|
||
const templatesQ = useQuery({
|
||
queryKey: ["communication", tenantId, "templates-select"],
|
||
queryFn: () => communicationApi.templates.list(tenantId!),
|
||
enabled: !!tenantId,
|
||
});
|
||
|
||
const sendM = useMutation({
|
||
mutationFn: (body: Record<string, unknown>) => communicationApi.messages.send(tenantId!, body),
|
||
onSuccess: (data) => {
|
||
toast.success(`${data.length} پیام در صف قرار گرفت`);
|
||
form.reset({ channel: "sms", priority: "normal", to_address: "", body: "", template_key: "", correlation_id: "", scheduled_at: "" });
|
||
},
|
||
onError: (e: Error) => toast.error(e.message),
|
||
});
|
||
|
||
if (!tenantId || templatesQ.isLoading) return <CommunicationPageLoader />;
|
||
if (!perms.can("communication.messages.send")) return <PermissionDeniedState />;
|
||
|
||
return (
|
||
<div className="mx-auto max-w-2xl space-y-6">
|
||
<CommunicationBreadcrumbs current="ارسال SMS" parent={{ href: "/communication/sms", label: "SMS" }} />
|
||
<PageHeader title="ارسال پیام SMS" description="ارسال فوری یا زمانبندیشده — متصل به backend واقعی" />
|
||
<Card>
|
||
<CardContent className="p-6">
|
||
<form
|
||
className="space-y-4"
|
||
onSubmit={form.handleSubmit((d) => {
|
||
const body: Record<string, unknown> = {
|
||
channel: d.channel,
|
||
to_address: d.to_address,
|
||
priority: d.priority,
|
||
process_immediately: !d.scheduled_at,
|
||
};
|
||
if (d.body) body.body = d.body;
|
||
if (d.template_key) body.template_key = d.template_key;
|
||
if (d.correlation_id) body.correlation_id = d.correlation_id;
|
||
if (d.scheduled_at) body.scheduled_at = new Date(d.scheduled_at).toISOString();
|
||
sendM.mutate(body);
|
||
})}
|
||
>
|
||
<FormField label="گیرنده (موبایل)">
|
||
<Input {...form.register("to_address", { required: true })} placeholder="09…" dir="ltr" />
|
||
</FormField>
|
||
<FormField label="متن">
|
||
<Textarea {...form.register("body")} rows={4} placeholder="متن پیام یا از قالب استفاده کنید" />
|
||
</FormField>
|
||
<FormField label="قالب (اختیاری)">
|
||
<Select {...form.register("template_key")}>
|
||
<option value="">— بدون قالب —</option>
|
||
{(templatesQ.data ?? []).map((t) => (
|
||
<option key={String(t.id)} value={String(t.template_key)}>
|
||
{String(t.name)} ({String(t.template_key)})
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</FormField>
|
||
<FormField label="اولویت">
|
||
<Select {...form.register("priority")}>
|
||
<option value="low">کم</option>
|
||
<option value="normal">عادی</option>
|
||
<option value="high">بالا</option>
|
||
<option value="critical">بحرانی</option>
|
||
</Select>
|
||
</FormField>
|
||
<FormField label="Correlation ID (idempotency)">
|
||
<Input {...form.register("correlation_id")} dir="ltr" />
|
||
</FormField>
|
||
<FormField label="زمانبندی (اختیاری)">
|
||
<Input type="datetime-local" {...form.register("scheduled_at")} />
|
||
</FormField>
|
||
<Button type="submit" loading={sendM.isPending}>ارسال</Button>
|
||
</form>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default SendMessagePage;
|