TorbatYar/frontend/modules/communication/features/sendMessage.tsx
Mortezakoohjani 10c3c43a75 feat(communication): add SMS MVP frontend with governance artifacts.
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>
2026-07-27 11:11:25 +03:30

108 lines
4.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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;