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:
parent
d579d0b142
commit
10c3c43a75
@ -1,11 +1,12 @@
|
|||||||
# Communication Service
|
# Communication Service
|
||||||
|
|
||||||
Independent **Enterprise Communication Platform** for TorbatYar SuperApp.
|
Independent **Enterprise Communication Platform** (**Torbat Communication**) for TorbatYar SuperApp.
|
||||||
|
|
||||||
- Database: `communication_db` (sole owner)
|
- Database: `communication_db` (sole owner)
|
||||||
- Port: `8005`
|
- Port: `8005`
|
||||||
- Version: `0.8.10.1`
|
- Version: `0.8.10.1`
|
||||||
- Permission prefix: `communication.*`
|
- Permission prefix: `communication.*`
|
||||||
|
- Status: **Production Ready (SMS MVP)**
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
@ -15,10 +16,14 @@ Consumers interact only via HTTP APIs and events. No module may call external SM
|
|||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
Designed for: SMS, Email, Push, WhatsApp, Telegram, Rubika, Voice, Future.
|
| Channel | Status |
|
||||||
|
| --- | --- |
|
||||||
**Initially active:** SMS (mock + Payamak adapters).
|
| SMS (mock + Payamak) | **Production Ready** |
|
||||||
|
| Email, Push, WhatsApp, Telegram, Rubika, Voice | Architecture-ready stubs — see [communication-roadmap.md](../../../docs/communication-roadmap.md) |
|
||||||
|
|
||||||
## Docs
|
## 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)
|
||||||
|
|||||||
@ -85,11 +85,14 @@
|
|||||||
| Backward Compatibility | Pass (additive) |
|
| Backward Compatibility | Pass (additive) |
|
||||||
| Tenant Isolation | Pass |
|
| 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
|
## Related Documents
|
||||||
|
|
||||||
- [communication-phase-8.md](communication-phase-8.md)
|
- [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)
|
- [ADR-012](architecture/adr/ADR-012.md)
|
||||||
- [progress.md](progress.md)
|
- [progress.md](progress.md)
|
||||||
- [quality-gates.md](ai-framework/quality-gates.md)
|
- [quality-gates.md](ai-framework/quality-gates.md)
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Status | Complete (validated) |
|
| Status | Complete — **Production Ready (SMS MVP)** |
|
||||||
| Module | communication |
|
| Module | communication |
|
||||||
|
| Commercial product | Torbat Communication |
|
||||||
| Version | 0.8.10.1 |
|
| Version | 0.8.10.1 |
|
||||||
| Database | `communication_db` |
|
| Database | `communication_db` |
|
||||||
| API Port | 8005 |
|
| API Port | 8005 |
|
||||||
@ -141,7 +142,7 @@ Architecture, health/ready/security, providers/SMS/failover cascade, queue/DLQ,
|
|||||||
|
|
||||||
## Known Limitations
|
## 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).
|
- Continuous Celery worker drain is optional ops work (API/`process_immediately` covers correctness).
|
||||||
- Real message bus consumers not wired (publish-only envelopes).
|
- Real message bus consumers not wired (publish-only envelopes).
|
||||||
- Sender unique constraint is not soft-delete-aware (recreate after soft-delete may conflict).
|
- 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] ADR-012 referenced
|
||||||
- [x] Module Registry updated
|
- [x] Module Registry updated
|
||||||
- [x] Progress updated
|
- [x] Progress updated
|
||||||
|
- [x] Service snapshot + phase handover generated
|
||||||
|
- [x] Production Ready (SMS MVP) declared
|
||||||
- [x] No TODO remains
|
- [x] No TODO remains
|
||||||
|
|
||||||
## Related Documents
|
## Related Documents
|
||||||
|
|
||||||
- [Progress](progress.md)
|
- [Progress](progress.md)
|
||||||
- [Module Registry](module-registry.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)
|
- [Audit Report](communication-phase-8-audit.md)
|
||||||
- [ADR-012](architecture/adr/ADR-012.md)
|
- [ADR-012](architecture/adr/ADR-012.md)
|
||||||
- [Service README](../backend/services/communication/README.md)
|
- [Service README](../backend/services/communication/README.md)
|
||||||
|
|||||||
67
docs/communication-roadmap.md
Normal file
67
docs/communication-roadmap.md
Normal 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)
|
||||||
155
docs/phase-handover/phase-8.md
Normal file
155
docs/phase-handover/phase-8.md
Normal 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.0–8.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)
|
||||||
116
docs/service-snapshots/communication.yaml
Normal file
116
docs/service-snapshots/communication.yaml
Normal 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"
|
||||||
53
frontend/app/api/communication/[...path]/route.ts
Normal file
53
frontend/app/api/communication/[...path]/route.ts
Normal 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);
|
||||||
|
}
|
||||||
5
frontend/app/communication/ai-campaigns/error.tsx
Normal file
5
frontend/app/communication/ai-campaigns/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/ai-campaigns/loading.tsx
Normal file
2
frontend/app/communication/ai-campaigns/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/ai-campaigns/page.tsx
Normal file
2
frontend/app/communication/ai-campaigns/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { AiCampaignsPage as default } from "@/modules/communication/features/aiCampaigns";
|
||||||
5
frontend/app/communication/analytics/error.tsx
Normal file
5
frontend/app/communication/analytics/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/analytics/loading.tsx
Normal file
2
frontend/app/communication/analytics/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/analytics/page.tsx
Normal file
2
frontend/app/communication/analytics/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { AnalyticsPage as default } from "@/modules/communication/features/monitoring";
|
||||||
5
frontend/app/communication/api-keys/error.tsx
Normal file
5
frontend/app/communication/api-keys/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/api-keys/loading.tsx
Normal file
2
frontend/app/communication/api-keys/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/api-keys/page.tsx
Normal file
2
frontend/app/communication/api-keys/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { ApiKeysPage as default } from "@/modules/communication/features/apiKeys";
|
||||||
5
frontend/app/communication/audit/error.tsx
Normal file
5
frontend/app/communication/audit/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/audit/loading.tsx
Normal file
2
frontend/app/communication/audit/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/audit/page.tsx
Normal file
2
frontend/app/communication/audit/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { AuditPage as default } from "@/modules/communication/features/audit";
|
||||||
5
frontend/app/communication/automation/error.tsx
Normal file
5
frontend/app/communication/automation/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/automation/loading.tsx
Normal file
2
frontend/app/communication/automation/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/automation/page.tsx
Normal file
2
frontend/app/communication/automation/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { MarketingAutomationPage as default } from "@/modules/communication/features/marketingAutomation";
|
||||||
5
frontend/app/communication/automation/triggers/error.tsx
Normal file
5
frontend/app/communication/automation/triggers/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/automation/triggers/page.tsx
Normal file
2
frontend/app/communication/automation/triggers/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { AutomationTriggersPage as default } from "@/modules/communication/features/automationTriggers";
|
||||||
@ -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} />;
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { CampaignOrchestratorPage as default } from "@/modules/communication/features/campaignOrchestrator";
|
||||||
5
frontend/app/communication/capabilities/error.tsx
Normal file
5
frontend/app/communication/capabilities/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/capabilities/loading.tsx
Normal file
2
frontend/app/communication/capabilities/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/capabilities/page.tsx
Normal file
2
frontend/app/communication/capabilities/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { CapabilitiesPage as default } from "@/modules/communication/features/capabilities";
|
||||||
5
frontend/app/communication/connectors/error.tsx
Normal file
5
frontend/app/communication/connectors/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/connectors/loading.tsx
Normal file
2
frontend/app/communication/connectors/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/connectors/page.tsx
Normal file
2
frontend/app/communication/connectors/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { ConnectorsPage as default } from "@/modules/communication/features/connectors";
|
||||||
@ -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} />;
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { AddressBooksPage as default } from "@/modules/communication/features/recipients";
|
||||||
5
frontend/app/communication/contacts/recipients/error.tsx
Normal file
5
frontend/app/communication/contacts/recipients/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/contacts/recipients/page.tsx
Normal file
2
frontend/app/communication/contacts/recipients/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { RecipientsPage as default } from "@/modules/communication/features/recipients";
|
||||||
5
frontend/app/communication/contacts/sources/error.tsx
Normal file
5
frontend/app/communication/contacts/sources/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/contacts/sources/loading.tsx
Normal file
2
frontend/app/communication/contacts/sources/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/contacts/sources/page.tsx
Normal file
2
frontend/app/communication/contacts/sources/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { ContactSourcesPage as default } from "@/modules/communication/features/contactSources";
|
||||||
5
frontend/app/communication/conversations/error.tsx
Normal file
5
frontend/app/communication/conversations/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/conversations/loading.tsx
Normal file
2
frontend/app/communication/conversations/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/conversations/page.tsx
Normal file
2
frontend/app/communication/conversations/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { ConversationsPage as default } from "@/modules/communication/features/conversations";
|
||||||
5
frontend/app/communication/dashboard/error.tsx
Normal file
5
frontend/app/communication/dashboard/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/dashboard/loading.tsx
Normal file
2
frontend/app/communication/dashboard/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/dashboard/page.tsx
Normal file
2
frontend/app/communication/dashboard/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { CommunicationDashboard as default } from "@/modules/communication/features/dashboard";
|
||||||
5
frontend/app/communication/email/error.tsx
Normal file
5
frontend/app/communication/email/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/email/loading.tsx
Normal file
2
frontend/app/communication/email/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/email/page.tsx
Normal file
2
frontend/app/communication/email/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { EmailPage as default } from "@/modules/communication/features/email";
|
||||||
5
frontend/app/communication/error.tsx
Normal file
5
frontend/app/communication/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
5
frontend/app/communication/health/error.tsx
Normal file
5
frontend/app/communication/health/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/health/loading.tsx
Normal file
2
frontend/app/communication/health/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/health/page.tsx
Normal file
2
frontend/app/communication/health/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { HealthPage as default } from "@/modules/communication/features/health";
|
||||||
5
frontend/app/communication/inbox/error.tsx
Normal file
5
frontend/app/communication/inbox/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/inbox/loading.tsx
Normal file
2
frontend/app/communication/inbox/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/inbox/page.tsx
Normal file
2
frontend/app/communication/inbox/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { OmnichannelInboxPage as default } from "@/modules/communication/features/omnichannelInbox";
|
||||||
5
frontend/app/communication/integrations/error.tsx
Normal file
5
frontend/app/communication/integrations/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/integrations/loading.tsx
Normal file
2
frontend/app/communication/integrations/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/integrations/page.tsx
Normal file
2
frontend/app/communication/integrations/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { IntegrationsHubPage as default } from "@/modules/communication/features/integrationsHub";
|
||||||
5
frontend/app/communication/journey-builder/error.tsx
Normal file
5
frontend/app/communication/journey-builder/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/journey-builder/loading.tsx
Normal file
2
frontend/app/communication/journey-builder/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/journey-builder/page.tsx
Normal file
2
frontend/app/communication/journey-builder/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { JourneyBuilderPage as default } from "@/modules/communication/features/journeyBuilder";
|
||||||
3
frontend/app/communication/layout.tsx
Normal file
3
frontend/app/communication/layout.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
"use client";
|
||||||
|
import { CommunicationRootLayout } from "@/modules/communication/components/createPortalLayout";
|
||||||
|
export default CommunicationRootLayout;
|
||||||
5
frontend/app/communication/live-chat/error.tsx
Normal file
5
frontend/app/communication/live-chat/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/live-chat/loading.tsx
Normal file
2
frontend/app/communication/live-chat/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/live-chat/page.tsx
Normal file
2
frontend/app/communication/live-chat/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { LiveChatPage as default } from "@/modules/communication/features/liveChat";
|
||||||
2
frontend/app/communication/loading.tsx
Normal file
2
frontend/app/communication/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
5
frontend/app/communication/monitoring/error.tsx
Normal file
5
frontend/app/communication/monitoring/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/monitoring/loading.tsx
Normal file
2
frontend/app/communication/monitoring/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/monitoring/page.tsx
Normal file
2
frontend/app/communication/monitoring/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { MonitoringPage as default } from "@/modules/communication/features/monitoring";
|
||||||
7
frontend/app/communication/not-found.tsx
Normal file
7
frontend/app/communication/not-found.tsx
Normal 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>} />
|
||||||
|
);
|
||||||
|
}
|
||||||
5
frontend/app/communication/notifications/error.tsx
Normal file
5
frontend/app/communication/notifications/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/notifications/loading.tsx
Normal file
2
frontend/app/communication/notifications/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/notifications/page.tsx
Normal file
2
frontend/app/communication/notifications/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { NotificationCenterPage as default } from "@/modules/communication/features/notificationCenter";
|
||||||
5
frontend/app/communication/otp/error.tsx
Normal file
5
frontend/app/communication/otp/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/otp/loading.tsx
Normal file
2
frontend/app/communication/otp/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/otp/page.tsx
Normal file
2
frontend/app/communication/otp/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { OtpPage as default } from "@/modules/communication/features/otp";
|
||||||
2
frontend/app/communication/page.tsx
Normal file
2
frontend/app/communication/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { ExecutiveDashboard as default } from "@/modules/communication/features/executiveDashboard";
|
||||||
5
frontend/app/communication/permissions/error.tsx
Normal file
5
frontend/app/communication/permissions/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/permissions/loading.tsx
Normal file
2
frontend/app/communication/permissions/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/permissions/page.tsx
Normal file
2
frontend/app/communication/permissions/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { PermissionsPage as default } from "@/modules/communication/features/permissions";
|
||||||
5
frontend/app/communication/providers/dashboard/error.tsx
Normal file
5
frontend/app/communication/providers/dashboard/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/providers/dashboard/page.tsx
Normal file
2
frontend/app/communication/providers/dashboard/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { ProviderDashboard as default } from "@/modules/communication/features/providerDashboard";
|
||||||
5
frontend/app/communication/push/error.tsx
Normal file
5
frontend/app/communication/push/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/push/loading.tsx
Normal file
2
frontend/app/communication/push/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/push/page.tsx
Normal file
2
frontend/app/communication/push/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { PushPage as default } from "@/modules/communication/features/push";
|
||||||
5
frontend/app/communication/reports/error.tsx
Normal file
5
frontend/app/communication/reports/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/reports/loading.tsx
Normal file
2
frontend/app/communication/reports/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/reports/page.tsx
Normal file
2
frontend/app/communication/reports/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { ReportsPage as default } from "@/modules/communication/features/monitoring";
|
||||||
5
frontend/app/communication/roles/error.tsx
Normal file
5
frontend/app/communication/roles/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/roles/loading.tsx
Normal file
2
frontend/app/communication/roles/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/roles/page.tsx
Normal file
2
frontend/app/communication/roles/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { RolesPage as default } from "@/modules/communication/features/roles";
|
||||||
5
frontend/app/communication/rubika/error.tsx
Normal file
5
frontend/app/communication/rubika/error.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
2
frontend/app/communication/rubika/loading.tsx
Normal file
2
frontend/app/communication/rubika/loading.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { LoadingState } from "@/components/ds";
|
||||||
|
export default function Loading() { return <LoadingState label="بارگذاری…" />; }
|
||||||
2
frontend/app/communication/rubika/page.tsx
Normal file
2
frontend/app/communication/rubika/page.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"use client";
|
||||||
|
export { RubikaPage as default } from "@/modules/communication/features/rubika";
|
||||||
5
frontend/app/communication/settings/error.tsx
Normal file
5
frontend/app/communication/settings/error.tsx
Normal 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
Loading…
Reference in New Issue
Block a user