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>
This commit is contained in:
Mortezakoohjani 2026-07-27 11:11:25 +03:30
parent d579d0b142
commit 10c3c43a75
218 changed files with 4327 additions and 9 deletions

View File

@ -1,11 +1,12 @@
# Communication Service
Independent **Enterprise Communication Platform** for TorbatYar SuperApp.
Independent **Enterprise Communication Platform** (**Torbat Communication**) for TorbatYar SuperApp.
- Database: `communication_db` (sole owner)
- Port: `8005`
- Version: `0.8.10.1`
- Permission prefix: `communication.*`
- Status: **Production Ready (SMS MVP)**
## Boundaries
@ -15,10 +16,14 @@ Consumers interact only via HTTP APIs and events. No module may call external SM
## Channels
Designed for: SMS, Email, Push, WhatsApp, Telegram, Rubika, Voice, Future.
**Initially active:** SMS (mock + Payamak adapters).
| Channel | Status |
| --- | --- |
| SMS (mock + Payamak) | **Production Ready** |
| Email, Push, WhatsApp, Telegram, Rubika, Voice | Architecture-ready stubs — see [communication-roadmap.md](../../../docs/communication-roadmap.md) |
## Docs
See `docs/communication-phase-8.md`.
- [communication-phase-8.md](../../../docs/communication-phase-8.md)
- [communication-roadmap.md](../../../docs/communication-roadmap.md)
- [phase-handover/phase-8.md](../../../docs/phase-handover/phase-8.md)
- [service-snapshots/communication.yaml](../../../docs/service-snapshots/communication.yaml)

View File

@ -85,11 +85,14 @@
| Backward Compatibility | Pass (additive) |
| Tenant Isolation | Pass |
**Verdict:** Enterprise Communication Platform meets production-ready quality for Phase 8 according to project standards.
**Verdict:** Enterprise Communication Platform meets production-ready quality for Phase 8 (SMS MVP) according to project standards. Service snapshot and phase handover generated 2026-07-26.
## Related Documents
- [communication-phase-8.md](communication-phase-8.md)
- [communication-roadmap.md](communication-roadmap.md)
- [phase-handover/phase-8.md](phase-handover/phase-8.md)
- [service-snapshots/communication.yaml](service-snapshots/communication.yaml)
- [ADR-012](architecture/adr/ADR-012.md)
- [progress.md](progress.md)
- [quality-gates.md](ai-framework/quality-gates.md)

View File

@ -2,8 +2,9 @@
| Field | Value |
| --- | --- |
| Status | Complete (validated) |
| Status | Complete **Production Ready (SMS MVP)** |
| Module | communication |
| Commercial product | Torbat Communication |
| Version | 0.8.10.1 |
| Database | `communication_db` |
| API Port | 8005 |
@ -141,7 +142,7 @@ Architecture, health/ready/security, providers/SMS/failover cascade, queue/DLQ,
## Known Limitations
- Email / Push / WhatsApp / Telegram / Rubika / Voice remain architecture-ready stubs.
- **MVP scope:** SMS is production-ready; Email / Push / WhatsApp / Telegram / Rubika / Voice remain architecture-ready stubs — see [communication-roadmap.md](communication-roadmap.md).
- Continuous Celery worker drain is optional ops work (API/`process_immediately` covers correctness).
- Real message bus consumers not wired (publish-only envelopes).
- Sender unique constraint is not soft-delete-aware (recreate after soft-delete may conflict).
@ -159,12 +160,17 @@ See [communication-phase-8-audit.md](communication-phase-8-audit.md).
- [x] ADR-012 referenced
- [x] Module Registry updated
- [x] Progress updated
- [x] Service snapshot + phase handover generated
- [x] Production Ready (SMS MVP) declared
- [x] No TODO remains
## Related Documents
- [Progress](progress.md)
- [Module Registry](module-registry.md)
- [Communication Roadmap](communication-roadmap.md)
- [Phase Handover](phase-handover/phase-8.md)
- [Service Snapshot](service-snapshots/communication.yaml)
- [Audit Report](communication-phase-8-audit.md)
- [ADR-012](architecture/adr/ADR-012.md)
- [Service README](../backend/services/communication/README.md)

View File

@ -0,0 +1,67 @@
# Communication Platform Roadmap
> **Torbat Communication** — Production Ready for **SMS MVP** (Phase 8 complete).
> Completed work → [progress.md](progress.md). Phase doc → [communication-phase-8.md](communication-phase-8.md).
> Handover → [phase-handover/phase-8.md](phase-handover/phase-8.md). Snapshot → [service-snapshots/communication.yaml](service-snapshots/communication.yaml).
The Communication service is **feature-complete for the current MVP scope**. Items below are **future roadmap only** — not registered phases and not in scope until explicitly product-scoped.
## Current Production Scope (Complete)
| Capability | Status |
| --- | --- |
| SMS (mock + Payamak) | Production Ready |
| Provider registry + failover + circuit breaker | Production Ready |
| Template engine | Production Ready |
| Dynamic contact sources | Production Ready |
| Queue + DLQ + delivery tracking | Production Ready |
| Business OTP (non-auth) | Production Ready |
| Incoming webhooks | Production Ready |
| Health / capabilities / monitoring | Production Ready |
## Future Channels (Not Implemented)
Architecture-ready stubs exist; real adapters are deferred:
| Channel | Provider kind (stub) | Notes |
| --- | --- | --- |
| Email | `smtp` | SMTP adapter + sender identities |
| Push | `fcm` | FCM + device token registration |
| WhatsApp | `whatsapp_cloud` | Cloud API + template approval alignment |
| Telegram | `telegram_bot` | Bot API adapter |
| Rubika | `rubika_bot` | Region-specific social messaging |
| Voice | `voice_gateway` | Call status webhooks; OTP voice path |
## Optional Operations Enhancements (Not MVP)
| Item | Notes |
| --- | --- |
| Dedicated Celery worker | Reuse `QueueEngine.process_due`; optional ops |
| Redis-backed rate limiter | Replace in-process limiter at scale |
| Prometheus metrics exposition | Platform ops standard TBD |
| Outbox dual-write / bus consumers | Platform message bus maturity |
| Core OTP handoff | Requires explicit ADR; auth OTP stays on Core today |
## Cross-Service Integration (Consumer-Owned)
Other services integrate **via Communication APIs/events only** — connector phases belong to those services:
| Consumer | Phase | Responsibility |
| --- | --- | --- |
| Delivery | 10.9 | Notification client + app API contracts |
| Experience | 11.9 | Widget + notify client contracts |
| Sports Center | 9.8 | CRM/Loyalty/Communication/Accounting adapters |
| Hospitality | 12.7 | Multi-platform connector bundle |
## Longer Term (Platform)
- Notification service as thin consumer of Communication ([roadmap.md](roadmap.md) item 26)
- Smart Messenger + legacy SMS panel alignment (prefer Communication per ADR-012)
## Related Documents
- [communication-phase-8.md](communication-phase-8.md)
- [communication-phase-8-audit.md](communication-phase-8-audit.md)
- [ADR-012](architecture/adr/ADR-012.md)
- [Module Registry](module-registry.md)
- [Provider Registry](provider-registry.md)

View File

@ -0,0 +1,155 @@
# Phase Handover — Communication 8 Enterprise Communication Platform
## Metadata
| Field | Value |
| --- | --- |
| Phase ID | communication-8 |
| Title | Enterprise Communication Platform (SMS-first MVP) |
| Status | Complete — **Production Ready (SMS)** |
| Service(s) | communication (`communication-service`, port 8005) |
| Commercial product | Torbat Communication |
| Version | 0.8.10.1 |
| Date | 2026-07-26 |
| ADR(s) | ADR-001, ADR-003, ADR-006, ADR-012 |
## Enterprise Phase Discovery Summary
| Item | Detail |
| --- | --- |
| Discovery Record location | [communication-phase-8.md](../communication-phase-8.md) |
| Gaps promoted & closed | Sub-phases 8.08.10 + validation hardening (idempotency, rate limits, permissions, metrics, OTP isolation) |
| Exclusions (future / other service) | Non-SMS channel adapters; Core auth OTP migration; Notification service consumer; Celery worker as optional ops |
| CRUD-only rejected | Yes — router, queue, failover, templates, contacts, OTP, webhooks, monitoring |
## Business Analysis Summary
| Item | Detail |
| --- | --- |
| Capabilities delivered | Independent SMS messaging platform: providers, router/failover, templates, dynamic contacts, queue/DLQ, delivery tracking, business OTP, webhooks, monitoring |
| Explicit non-goals honored | No foreign DB ownership; no provider code in business modules; auth OTP stays on Core; future channels remain stubs |
## Reusable Components
| Component | Location | Reuse notes |
| --- | --- | --- |
| ChannelProvider protocol | `app/providers/contracts.py` | Extend for future channel adapters |
| Provider registry | `app/providers/__init__.py` | Maps `provider_kind` → adapter |
| CommunicationRouter | `app/services/router.py` | Priority, failover, circuit breaker |
| QueueEngine | `app/services/queue_engine.py` | Async drain, DLQ, rate-limit deferral |
| TemplateService | `app/services/template_service.py` | Variables, locale, approval workflow |
| ContactService | `app/services/contact_service.py` | Manual, CSV, remote source resolve |
| OTPService | `app/services/otp_service.py` | Business OTP; rejects Core reserved purposes |
| WebhookService | `app/services/webhook_service.py` | HMAC + external_id dedupe |
## Public APIs
| Method | Path | Auth / Permission | Notes |
| --- | --- | --- | --- |
| GET | `/health` | public | Liveness |
| GET | `/health/ready` | public | DB readiness |
| GET | `/capabilities` | public | Channels, features, contact source types |
| GET | `/metrics` | public | Service feature summary |
| GET/POST | `/api/v1/providers` | `communication.providers.*` | Provider CRUD, status, balance, health |
| POST | `/api/v1/providers/senders` | `communication.senders.manage` | Sender numbers |
| GET/POST | `/api/v1/templates` | `communication.templates.*` | Template CRUD, approve/reject, preview |
| GET/POST | `/api/v1/contacts/*` | `communication.contacts.*` / `communication.sources.*` | Manual, CSV, sources, resolve |
| POST | `/api/v1/messages/send` | `communication.messages.send` | Idempotent on correlation_id + to_address |
| GET | `/api/v1/messages` | `communication.messages.view` | List/filter messages |
| POST | `/api/v1/messages/queue/process` | `communication.queue.manage` | Process due queue items |
| GET | `/api/v1/messages/queue/dead-letters` | `communication.queue.view` | DLQ inspection |
| POST | `/api/v1/otp/request` | `communication.otp.request` | Business OTP generate |
| POST | `/api/v1/otp/verify` | `communication.otp.verify` | Business OTP verify |
| POST | `/api/v1/webhooks/incoming` | `communication.webhooks.manage` | Provider delivery callbacks |
| GET | `/api/v1/monitoring/stats` | `communication.monitoring.view` | Message/queue/provider snapshot |
| GET | `/api/v1/monitoring/metrics` | `communication.monitoring.view` | Latency, failure rate, deferrals |
## Events
| Event type | Domain / Integration | Payload summary | Version |
| --- | --- | --- | --- |
| `communication.message.queued` | Domain | channel, to, correlation_id | 1 |
| `communication.message.sent` | Domain | provider, provider_message_id | 1 |
| `communication.message.delivered` | Domain | delivery confirmation | 1 |
| `communication.message.failed` | Domain | error, terminal failure | 1 |
| `communication.provider.failover` | Domain | from/to provider | 1 |
| `communication.otp.*` | Domain | OTP lifecycle | 1 |
| `communication.queue.dead_letter` | Domain | queue item exhausted retries | 1 |
| `communication.webhook.received` | Domain | inbound provider webhook | 1 |
## Permissions
| Permission | Routes / actions |
| --- | --- |
| `communication.providers.*` | Provider and sender management |
| `communication.templates.*` | Template CRUD and approval |
| `communication.contacts.*` / `communication.sources.*` | Contacts and dynamic sources |
| `communication.messages.*` | Send, view, cancel |
| `communication.queue.*` | Queue process and DLQ |
| `communication.otp.*` | OTP request/verify |
| `communication.webhooks.manage` | Incoming webhooks |
| `communication.monitoring.view` | Stats and metrics |
## Extension Points
| Extension point | How to extend | Forbidden uses |
| --- | --- | --- |
| ChannelProvider | Register new adapter in provider registry | Provider code in business modules |
| Contact source types | Add resolver for new remote source | Copy foreign-module rows into communication_db |
| Template variables | Extend template schema + validators | Bypass approval for production sends |
| Queue worker | Optional Celery entry via `QueueEngine.process_due` | Blocking business transactions on send failure |
## Known Limitations
- **MVP scope:** SMS active (mock + Payamak); Email/Push/WhatsApp/Telegram/Rubika/Voice are stubs — see [communication-roadmap.md](../communication-roadmap.md)
- No dedicated Celery worker (optional ops; API path is correct)
- Publish-only event envelopes (no wired bus consumers yet)
- In-process rate limiter (per-instance)
- Sender unique constraint not soft-delete-aware
- Core auth OTP remains on Core (ADR-012 neutral note)
## Migration Notes
| Item | Detail |
| --- | --- |
| Alembic revision(s) | `0001_initial`, `0002_validation_hardening` |
| Upgrade steps | `alembic upgrade head` in communication service |
| Downgrade support | Yes |
| Data backfill | None |
| Breaking changes | None (additive validation hardening) |
## Dependencies
| Dependency | Type | Required for |
| --- | --- | --- |
| onboarding-4 | Phase | Core platform baseline |
| shared-lib | Library | Events, security, exceptions |
| Payamak / mock SMS | External provider | SMS delivery |
## Next Phase Entry
Communication is **feature-complete for the current MVP scope (SMS only)**. No registered Communication phases remain.
| Field | Value |
| --- | --- |
| Recommended next phase | None (MVP complete) |
| Blockers for future channels | Explicit product decision + new phase registration when scoped |
| Entry checklist | Read this handover + [service-snapshots/communication.yaml](../service-snapshots/communication.yaml); future channels via [communication-roadmap.md](../communication-roadmap.md) only |
## Completion Sign-Off
- [x] Quality gates passed (see [communication-phase-8-audit.md](../communication-phase-8-audit.md))
- [x] Tests green (42)
- [x] Documentation updated
- [x] Service snapshot generated
- [x] Progress / registries / manifests updated
- [x] Production Ready (SMS) declared
- [x] No TODO for claimed deliverables
## Related Documents
- [communication-phase-8.md](../communication-phase-8.md)
- [communication-phase-8-audit.md](../communication-phase-8-audit.md)
- [communication-roadmap.md](../communication-roadmap.md)
- [service-snapshots/communication.yaml](../service-snapshots/communication.yaml)
- [ADR-012](../architecture/adr/ADR-012.md)

View File

@ -0,0 +1,116 @@
# Communication Platform service snapshot — MVP feature-complete (SMS).
# Spec: docs/ai-framework/service-snapshot-policy.md
schema_version: 1
snapshot_version: 1
service_name: communication
commercial_product: Torbat Communication
current_version: "0.8.10.1"
current_phase: communication-8
last_completed_phase: communication-8
next_phase: null
completed_phases:
- communication-8
remaining_phases: []
registered_modules:
- providers
- senders
- templates
- contacts
- contact_sources
- messages
- queue
- delivery_tracking
- otp
- webhooks
- monitoring
- audit
enabled_capabilities:
- sms_messaging
- provider_registry
- communication_router
- template_engine
- dynamic_contact_sources
- queue_engine
- delivery_tracking
- otp_platform
- incoming_webhooks
- monitoring
- production_ready_sms_mvp
enabled_bundles:
- torbat_communication
public_apis:
- { method: GET, path: /health, permission: public }
- { method: GET, path: /health/ready, permission: public }
- { method: GET, path: /capabilities, permission: public }
- { method: GET, path: /metrics, permission: public }
- { method: GET, path: /api/v1/providers, permission: communication.providers.view }
- { method: POST, path: /api/v1/providers, permission: communication.providers.manage }
- { method: GET, path: /api/v1/providers/status, permission: communication.providers.view }
- { method: POST, path: /api/v1/providers/senders, permission: communication.senders.manage }
- { method: GET, path: /api/v1/templates, permission: communication.templates.view }
- { method: POST, path: /api/v1/templates, permission: communication.templates.manage }
- { method: POST, path: "/api/v1/templates/{id}/approve", permission: communication.templates.approve }
- { method: GET, path: /api/v1/contacts/manual, permission: communication.contacts.view }
- { method: POST, path: /api/v1/contacts/sources, permission: communication.sources.manage }
- { method: POST, path: /api/v1/contacts/resolve, permission: communication.contacts.view }
- { method: POST, path: /api/v1/messages/send, permission: communication.messages.send }
- { method: GET, path: /api/v1/messages, permission: communication.messages.view }
- { method: POST, path: "/api/v1/messages/{id}/cancel", permission: communication.messages.cancel }
- { method: POST, path: /api/v1/messages/queue/process, permission: communication.queue.manage }
- { method: GET, path: /api/v1/messages/queue/dead-letters, permission: communication.queue.view }
- { method: POST, path: /api/v1/otp/request, permission: communication.otp.request }
- { method: POST, path: /api/v1/otp/verify, permission: communication.otp.verify }
- { method: POST, path: /api/v1/webhooks/incoming, permission: communication.webhooks.manage }
- { method: GET, path: /api/v1/monitoring/stats, permission: communication.monitoring.view }
- { method: GET, path: /api/v1/monitoring/metrics, permission: communication.monitoring.view }
published_events:
- { event_type: communication.message.queued, version: "1" }
- { event_type: communication.message.sent, version: "1" }
- { event_type: communication.message.delivered, version: "1" }
- { event_type: communication.message.failed, version: "1" }
- { event_type: communication.message.cancelled, version: "1" }
- { event_type: communication.message.expired, version: "1" }
- { event_type: communication.provider.failover, version: "1" }
- { event_type: communication.provider.circuit_opened, version: "1" }
- { event_type: communication.provider.circuit_closed, version: "1" }
- { event_type: communication.template.approved, version: "1" }
- { event_type: communication.template.rejected, version: "1" }
- { event_type: communication.otp.generated, version: "1" }
- { event_type: communication.otp.verified, version: "1" }
- { event_type: communication.otp.failed, version: "1" }
- { event_type: communication.queue.dead_letter, version: "1" }
- { event_type: communication.webhook.received, version: "1" }
permission_prefix: communication.*
active_adrs:
- ADR-001
- ADR-003
- ADR-006
- ADR-012
integration_contracts:
- ChannelProvider (in-service protocol)
- CRM / Loyalty / Sports / Restaurant / Marketplace / Accounting contact resolve (HTTP only)
- Core auth OTP remains on Core (no shared tables)
known_limitations:
- MVP scope is SMS-only; Email/Push/WhatsApp/Telegram/Rubika/Voice are architecture-ready stubs
- Continuous Celery worker drain is optional ops work (API/process_immediately covers correctness)
- Real message bus consumers not wired (publish-only envelopes)
- Sender unique constraint is not soft-delete-aware
- In-process rate limiter is per-instance (acceptable for MVP)
- Future channels documented in communication-roadmap.md only — no registered 9.x phases
open_todos: []
last_handover_reference: docs/phase-handover/phase-8.md
last_updated: "2026-07-26"

View File

@ -0,0 +1,53 @@
import { NextRequest, NextResponse } from "next/server";
const UPSTREAM =
process.env.COMMUNICATION_SERVICE_URL ||
process.env.NEXT_PUBLIC_COMMUNICATION_API_URL ||
"http://localhost:8005";
async function proxy(req: NextRequest, pathSegments: string[]) {
const p = pathSegments.join("/");
const url = new URL(req.url);
const target = `${UPSTREAM.replace(/\/$/, "")}/${p}${url.search}`;
const headers = new Headers();
for (const h of ["authorization", "content-type", "x-tenant-id", "accept"]) {
const v = req.headers.get(h);
if (v) headers.set(h, v);
}
let body: ArrayBuffer | undefined;
if (req.method !== "GET" && req.method !== "HEAD") body = await req.arrayBuffer();
try {
const res = await fetch(target, { method: req.method, headers, body, cache: "no-store" });
const out = new Headers();
const ct = res.headers.get("content-type");
if (ct) out.set("content-type", ct);
return new NextResponse(await res.arrayBuffer(), { status: res.status, headers: out });
} catch (err) {
const detail = err instanceof Error ? err.message : "upstream_unreachable";
return NextResponse.json(
{ error: { code: "communication_proxy_error", message: `پروکسی تربت ارتباط — ${detail}` } },
{ status: 502 }
);
}
}
export async function GET(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
const { path: p } = await ctx.params;
return proxy(req, p);
}
export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
const { path: p } = await ctx.params;
return proxy(req, p);
}
export async function PATCH(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
const { path: p } = await ctx.params;
return proxy(req, p);
}
export async function PUT(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
const { path: p } = await ctx.params;
return proxy(req, p);
}
export async function DELETE(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
const { path: p } = await ctx.params;
return proxy(req, p);
}

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { AiCampaignsPage as default } from "@/modules/communication/features/aiCampaigns";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { AnalyticsPage as default } from "@/modules/communication/features/monitoring";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { ApiKeysPage as default } from "@/modules/communication/features/apiKeys";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { AuditPage as default } from "@/modules/communication/features/audit";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { MarketingAutomationPage as default } from "@/modules/communication/features/marketingAutomation";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { AutomationTriggersPage as default } from "@/modules/communication/features/automationTriggers";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { CampaignOrchestratorPage as default } from "@/modules/communication/features/campaignOrchestrator";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { CapabilitiesPage as default } from "@/modules/communication/features/capabilities";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { ConnectorsPage as default } from "@/modules/communication/features/connectors";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { AddressBooksPage as default } from "@/modules/communication/features/recipients";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { RecipientsPage as default } from "@/modules/communication/features/recipients";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { ContactSourcesPage as default } from "@/modules/communication/features/contactSources";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { ConversationsPage as default } from "@/modules/communication/features/conversations";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { CommunicationDashboard as default } from "@/modules/communication/features/dashboard";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { EmailPage as default } from "@/modules/communication/features/email";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { HealthPage as default } from "@/modules/communication/features/health";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { OmnichannelInboxPage as default } from "@/modules/communication/features/omnichannelInbox";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { IntegrationsHubPage as default } from "@/modules/communication/features/integrationsHub";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { JourneyBuilderPage as default } from "@/modules/communication/features/journeyBuilder";

View File

@ -0,0 +1,3 @@
"use client";
import { CommunicationRootLayout } from "@/modules/communication/components/createPortalLayout";
export default CommunicationRootLayout;

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { LiveChatPage as default } from "@/modules/communication/features/liveChat";

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { MonitoringPage as default } from "@/modules/communication/features/monitoring";

View File

@ -0,0 +1,7 @@
import Link from "next/link";
import { EmptyState, Button } from "@/components/ds";
export default function NotFound() {
return (
<EmptyState title="صفحه یافت نشد" description="مسیر ارتباطات نامعتبر است." action={<Link href="/communication"><Button>داشبورد</Button></Link>} />
);
}

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { NotificationCenterPage as default } from "@/modules/communication/features/notificationCenter";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { OtpPage as default } from "@/modules/communication/features/otp";

View File

@ -0,0 +1,2 @@
"use client";
export { ExecutiveDashboard as default } from "@/modules/communication/features/executiveDashboard";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { PermissionsPage as default } from "@/modules/communication/features/permissions";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { ProviderDashboard as default } from "@/modules/communication/features/providerDashboard";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { PushPage as default } from "@/modules/communication/features/push";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { ReportsPage as default } from "@/modules/communication/features/monitoring";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { RolesPage as default } from "@/modules/communication/features/roles";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,2 @@
import { LoadingState } from "@/components/ds";
export default function Loading() { return <LoadingState label="بارگذاری…" />; }

View File

@ -0,0 +1,2 @@
"use client";
export { RubikaPage as default } from "@/modules/communication/features/rubika";

View File

@ -0,0 +1,5 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

Some files were not shown because too many files have changed in this diff Show More