diff --git a/app/selfserve/composer.lock b/app/selfserve/composer.lock index 1547b04a5f..2509701d27 100644 --- a/app/selfserve/composer.lock +++ b/app/selfserve/composer.lock @@ -3983,16 +3983,16 @@ }, { "name": "olcs/olcs-common", - "version": "5.0.0-beta.10", + "version": "v5.1.1", "source": { "type": "git", "url": "https://github.com/dvsa/olcs-common.git", - "reference": "530e47d0d8bc3574ded6ed980694c866894979ba" + "reference": "73b1c0cf809b50138dd1f5b471905361f1cd868e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dvsa/olcs-common/zipball/530e47d0d8bc3574ded6ed980694c866894979ba", - "reference": "530e47d0d8bc3574ded6ed980694c866894979ba", + "url": "https://api.github.com/repos/dvsa/olcs-common/zipball/73b1c0cf809b50138dd1f5b471905361f1cd868e", + "reference": "73b1c0cf809b50138dd1f5b471905361f1cd868e", "shasum": "" }, "require": { @@ -4050,9 +4050,9 @@ "notification-url": "https://packagist.org/downloads/", "description": "Common library for the OLCS Project", "support": { - "source": "https://github.com/dvsa/olcs-common/tree/5.0.0-beta.10" + "source": "https://github.com/dvsa/olcs-common/tree/v5.1.1" }, - "time": "2024-02-12T10:53:54+00:00" + "time": "2024-02-16T10:40:20+00:00" }, { "name": "olcs/olcs-logging", @@ -4108,16 +4108,16 @@ }, { "name": "olcs/olcs-transfer", - "version": "5.0.0-beta.8", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/dvsa/olcs-transfer.git", - "reference": "9b20f1f18e02c42775e85b87ed362765a2177ed3" + "reference": "b1fe910e1dafbf495367d16fe1953cf1aa9f88b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dvsa/olcs-transfer/zipball/9b20f1f18e02c42775e85b87ed362765a2177ed3", - "reference": "9b20f1f18e02c42775e85b87ed362765a2177ed3", + "url": "https://api.github.com/repos/dvsa/olcs-transfer/zipball/b1fe910e1dafbf495367d16fe1953cf1aa9f88b6", + "reference": "b1fe910e1dafbf495367d16fe1953cf1aa9f88b6", "shasum": "" }, "require": { @@ -4157,9 +4157,9 @@ "notification-url": "https://packagist.org/downloads/", "description": "OLCS Transfer", "support": { - "source": "https://github.com/dvsa/olcs-transfer/tree/5.0.0-beta.8" + "source": "https://github.com/dvsa/olcs-transfer/tree/v5.1.0" }, - "time": "2024-01-25T22:35:27+00:00" + "time": "2024-02-16T10:21:39+00:00" }, { "name": "olcs/olcs-utils", diff --git a/app/selfserve/module/Olcs/config/module.config.php b/app/selfserve/module/Olcs/config/module.config.php index b29b92eebd..eb30f7230e 100644 --- a/app/selfserve/module/Olcs/config/module.config.php +++ b/app/selfserve/module/Olcs/config/module.config.php @@ -18,6 +18,7 @@ use Olcs\Controller\Factory\IndexControllerFactory; use Olcs\Controller\Factory\MyDetailsControllerFactory; use Olcs\Controller\IndexController; +use Common\Service\Data as CommonDataService; use Olcs\Controller\Licence\Vehicle\ListVehicleController; use Olcs\Controller\Lva\Adapters\ApplicationPeopleAdapter; use Olcs\Controller\Lva\Adapters\LicencePeopleAdapter; @@ -45,6 +46,7 @@ use Olcs\Logging\Log\Processor\CorrelationId; use Olcs\Logging\Log\Processor\CorrelationIdFactory; use Olcs\Service\Cookie as CookieService; +use Olcs\Service\Data as DataService; use Olcs\Service\Processing as ProcessingService; use Olcs\Service\Qa as QaService; use Olcs\Session\LicenceVehicleManagement; @@ -437,6 +439,37 @@ ] ] ], + 'conversations' => [ + 'type' => 'segment', + 'options' => [ + 'route' => '/conversations[/]', + 'defaults' => [ + 'controller' => Olcs\Controller\ConversationsController::class, + 'action' => 'index' + ] + ], + 'may_terminate' => true, + 'child_routes' => [ + 'view' => [ + 'type' => 'segment', + 'options' => [ + 'route' => ':conversationId[/]', + 'defaults' => [ + 'action' => 'view', + ], + ] + ], + 'new' => [ + 'type' => 'segment', + 'options' => [ + 'route' => 'new[/]', + 'defaults' => [ + 'action' => 'add', + ], + ] + ], + ] + ], 'create_variation' => [ 'type' => 'segment', 'options' => [ @@ -1044,6 +1077,11 @@ 'label' => 'dashboard-nav-documents', 'route' => 'correspondence', ), + array( + 'id' => 'dashboard-messaging', + 'label' => 'dashboard-nav-messaging', + 'route' => 'conversations', + ), ), ), ), @@ -1301,25 +1339,26 @@ Olcs\Controller\Licence\Surrender\PrintSignReturnController::class => Olcs\Controller\Licence\Surrender\PrintSignReturnControllerFactory::class, \Olcs\Controller\Licence\Surrender\InformationChangedController::class => \Olcs\Controller\Licence\Surrender\InformationChangedControllerFactory::class, // Licence - Vehicles - \Olcs\Controller\Licence\Vehicle\AddVehicleSearchController::class => \Olcs\Controller\Licence\Vehicle\AddVehicleSearchControllerFactory::class, - \Olcs\Controller\Licence\Vehicle\AddDuplicateVehicleController::class => \Olcs\Controller\Licence\Vehicle\AddDuplicateVehicleControllerFactory::class, - \Olcs\Controller\Licence\Vehicle\RemoveVehicleController::class => \Olcs\Controller\Licence\Vehicle\RemoveVehicleControllerFactory::class, - \Olcs\Controller\Licence\Vehicle\RemoveVehicleConfirmationController::class => \Olcs\Controller\Licence\Vehicle\RemoveVehicleConfirmationControllerFactory::class, - \Olcs\Controller\Licence\Vehicle\TransferVehicleController::class => \Olcs\Controller\Licence\Vehicle\TransferVehicleControllerFactory::class, - \Olcs\Controller\Licence\Vehicle\ViewVehicleController::class => \Olcs\Controller\Licence\Vehicle\ViewVehicleControllerFactory::class, - \Olcs\Controller\Licence\Vehicle\TransferVehicleConfirmationController::class => \Olcs\Controller\Licence\Vehicle\TransferVehicleConfirmationControllerFactory::class, - \Olcs\Controller\Licence\Vehicle\Reprint\ReprintLicenceVehicleDiscController::class => \Olcs\Controller\Licence\Vehicle\Reprint\ReprintLicenceVehicleDiscControllerFactory::class, + \Olcs\Controller\Licence\Vehicle\AddVehicleSearchController::class => \Olcs\Controller\Licence\Vehicle\AddVehicleSearchControllerFactory::class, + \Olcs\Controller\Licence\Vehicle\AddDuplicateVehicleController::class => \Olcs\Controller\Licence\Vehicle\AddDuplicateVehicleControllerFactory::class, + \Olcs\Controller\Licence\Vehicle\RemoveVehicleController::class => \Olcs\Controller\Licence\Vehicle\RemoveVehicleControllerFactory::class, + \Olcs\Controller\Licence\Vehicle\RemoveVehicleConfirmationController::class => \Olcs\Controller\Licence\Vehicle\RemoveVehicleConfirmationControllerFactory::class, + \Olcs\Controller\Licence\Vehicle\TransferVehicleController::class => \Olcs\Controller\Licence\Vehicle\TransferVehicleControllerFactory::class, + \Olcs\Controller\Licence\Vehicle\ViewVehicleController::class => \Olcs\Controller\Licence\Vehicle\ViewVehicleControllerFactory::class, + \Olcs\Controller\Licence\Vehicle\TransferVehicleConfirmationController::class => \Olcs\Controller\Licence\Vehicle\TransferVehicleConfirmationControllerFactory::class, + \Olcs\Controller\Licence\Vehicle\Reprint\ReprintLicenceVehicleDiscController::class => \Olcs\Controller\Licence\Vehicle\Reprint\ReprintLicenceVehicleDiscControllerFactory::class, \Olcs\Controller\Licence\Vehicle\Reprint\ReprintLicenceVehicleDiscConfirmationController::class => \Olcs\Controller\Licence\Vehicle\Reprint\ReprintLicenceVehicleDiscConfirmationControllerFactory::class, - PromptController::class => \Olcs\Controller\PromptControllerFactory::class, + Olcs\Controller\ConversationsController::class => Olcs\Controller\Factory\ConversationsControllerFactory::class, + PromptController::class => \Olcs\Controller\PromptControllerFactory::class, // Process Signature from GOV.UK Account - \Olcs\Controller\SignatureVerificationController::class => \Olcs\Controller\SignatureVerificationControllerFactory::class, + \Olcs\Controller\SignatureVerificationController::class => \Olcs\Controller\SignatureVerificationControllerFactory::class, // LVA Controller Factories - LvaLicenceControllers\AddressesController::class => LvaLicenceControllerFactories\AddressesControllerFactory::class, - LvaLicenceControllers\BusinessDetailsController::class => LvaLicenceControllerFactories\BusinessDetailsControllerFactory::class, - LvaLicenceControllers\BusinessTypeController::class => LvaLicenceControllerFactories\BusinessTypeControllerFactory::class, - LvaLicenceControllers\ConditionsUndertakingsController::class => LvaLicenceControllerFactories\ConditionsUndertakingsControllerFactory::class, - LvaLicenceControllers\DiscsController::class => LvaLicenceControllerFactories\DiscsControllerFactory::class, - LvaLicenceControllers\OperatingCentresController::class => LvaLicenceControllerFactories\OperatingCentresControllerFactory::class, + LvaLicenceControllers\AddressesController::class => LvaLicenceControllerFactories\AddressesControllerFactory::class, + LvaLicenceControllers\BusinessDetailsController::class => LvaLicenceControllerFactories\BusinessDetailsControllerFactory::class, + LvaLicenceControllers\BusinessTypeController::class => LvaLicenceControllerFactories\BusinessTypeControllerFactory::class, + LvaLicenceControllers\ConditionsUndertakingsController::class => LvaLicenceControllerFactories\ConditionsUndertakingsControllerFactory::class, + LvaLicenceControllers\DiscsController::class => LvaLicenceControllerFactories\DiscsControllerFactory::class, + LvaLicenceControllers\OperatingCentresController::class => LvaLicenceControllerFactories\OperatingCentresControllerFactory::class, LvaLicenceControllers\OverviewController::class => LvaLicenceControllerFactories\OverviewControllerFactory::class, LvaLicenceControllers\PeopleController::class => LvaLicenceControllerFactories\PeopleControllerFactory::class, LvaLicenceControllers\SafetyController::class => LvaLicenceControllerFactories\SafetyControllerFactory::class, @@ -1425,7 +1464,7 @@ VariationTransportManagerAdapter::class => VariationTransportManagerAdapterFactory::class, VariationPeopleAdapter::class => VariationPeopleAdapterFactory::class, \Olcs\Logging\Log\Processor\CorrelationId::class => \Olcs\Logging\Log\Processor\CorrelationIdFactory::class, - ] + ], ), 'log_processors' => [ 'factories' => [ @@ -1467,6 +1506,11 @@ 'simple_date_format' => array( 'default' => 'd-m-Y' ), + 'data_services' => [ + 'factories' => [ + DataService\MessagingAppOrLicNo::class => CommonDataService\AbstractListDataServiceFactory::class, + ], + ], 'view_helpers' => array( 'factories' => [ \Olcs\View\Helper\SessionTimeoutWarning\SessionTimeoutWarning::class => \Olcs\View\Helper\SessionTimeoutWarning\SessionTimeoutWarningFactory::class, diff --git a/app/selfserve/module/Olcs/src/Controller/ConversationsController.php b/app/selfserve/module/Olcs/src/Controller/ConversationsController.php new file mode 100644 index 0000000000..026c9d3fa8 --- /dev/null +++ b/app/selfserve/module/Olcs/src/Controller/ConversationsController.php @@ -0,0 +1,218 @@ + [FeatureToggle::MESSAGING], + ]; + + protected FlashMessengerHelperService $flashMessengerHelper; + protected TableFactory $tableFactory; + protected FormHelperService $formHelperService; + + public function __construct( + NiTextTranslation $niTextTranslationUtil, + AuthorizationService $authService, + FlashMessengerHelperService $flashMessengerHelper, + TableFactory $tableFactory, + FormHelperService $formHelperService + ) { + $this->flashMessengerHelper = $flashMessengerHelper; + $this->tableFactory = $tableFactory; + $this->formHelperService = $formHelperService; + + parent::__construct($niTextTranslationUtil, $authService); + } + + public function indexAction(): ViewModel + { + $params = [ + 'page' => $this->params()->fromQuery('page', 1), + 'limit' => $this->params()->fromQuery('limit', 10), + 'sort' => $this->params()->fromQuery('sort', 'd.issuedDate'), + 'order' => $this->params()->fromQuery('order', 'DESC'), + 'organisation' => $this->getCurrentOrganisationId(), + 'query' => $this->params()->fromQuery(), + ]; + + $response = $this->handleQuery(ByOrganisationQuery::create($params)); + + if ($response->isOk()) { + $messages = $response->getResult(); + } else { + $this->flashMessengerHelper->addErrorMessage('unknown-error'); + $messages = []; + } + + $table = $this->tableFactory + ->buildTable('messages', $messages, $params); + + $view = new ViewModel(['table' => $table]); + $view->setTemplate('messages'); + + return $view; + } + + /** + * @throws \Exception + */ + public function addAction(): ViewModel + { + $form = $this->formHelperService->createForm(CreateForm::class, true, false); + if ($this->getRequest()->isPost()) { + $form->setData($this->getRequest()->getPost()); + if ($form->isValid()) { + return $this->submitConversation($form); + } + } + + $view = new ViewModel(); + $view->setVariable('form', $form); + $view->setTemplate('messages-new'); + + return $view; + } + + /** + * @return Response|ViewModel + * @throws \Exception + */ + private function submitConversation(\Laminas\Form\Form $form) + { + $response = $this->handleCommand($this->mapFormDataToCommand($form)); + if (!$response->isOk()) { + $this->flashMessengerHelper->addErrorMessage('There was an server error when submitting your conversation; please try later'); + return $this->addAction(); + } + + $conversationId = $response->getResult()['id']['conversation'] ?? null; + if (empty($conversationId)) { + $this->flashMessengerHelper->addErrorMessage('There was an server error when submitting your conversation; please try later'); + return $this->addAction(); + } + + $this->flashMessengerHelper->addSuccessMessage('Conversation was created successfully'); + return $this->redirect()->toRoute('conversations/view', ['conversationId' => $conversationId]); + } + + /** + * @throws \Exception + */ + private function mapFormDataToCommand(\Laminas\Form\Form $form): Create + { + $data = $form->getData(); + $processedData = [ + 'messageSubject' => $data['form-actions']['messageSubject'], + 'messageContent' => $data['form-actions']['messageContent'], + ]; + + $appOrLicNoPrefix = substr($data['form-actions']['appOrLicNo'], 0, 1); + $appOrLicNoSuffix = substr($data['form-actions']['appOrLicNo'], 1); + switch($appOrLicNoPrefix) { + case MessagingAppOrLicNo::PREFIX_LICENCE: + $processedData['licence'] = $appOrLicNoSuffix; + break; + case MessagingAppOrLicNo::PREFIX_APPLICATION: + $processedData['application'] = $appOrLicNoSuffix; + break; + default: + throw new \Exception('Invalid prefix on appOrLicNo'); + } + + return Create::create($processedData); + } + + /** @return ViewModel|Response */ + public function viewAction() + { + $params = [ + 'page' => $this->params()->fromQuery('page', 1), + 'limit' => $this->params()->fromQuery('limit', 10), + 'conversation' => $this->params()->fromRoute('conversationId'), + 'query' => $this->params()->fromQuery(), + ]; + + $response = $this->handleQuery(ByConversationQuery::create($params)); + + if ($response->isOk()) { + $messages = $response->getResult(); + } else { + $this->flashMessengerHelper->addErrorMessage('unknown-error'); + $messages = []; + } + + $form = $this->formHelperService->createForm(ReplyForm::class, true, false); + $this->formHelperService->setFormActionFromRequest($form, $this->getRequest()); + + $table = $this->tableFactory + ->buildTable('messages-view', $messages, $params); + + $view = new ViewModel( + [ + 'table' => $table, + 'form' => $form, + ], + ); + $view->setTemplate('messages-view'); + + if ($this->getRequest()->isPost() && $this->params()->fromPost('action') === 'reply') { + return $this->parseReply($view, $form); + } + + return $view; + } + + /** @return Response|ViewModel */ + protected function parseReply(ViewModel $view, Form $form) + { + $form->setData((array)$this->params()->fromPost()); + $form->get('id')->setValue($this->params()->fromRoute('conversation')); + + if (!$form->isValid()) { + return $view; + } + + $response = $this->handleCommand( + CreateMessageCommand::create( + [ + 'conversation' => $this->params()->fromRoute('conversationId'), + 'messageContent' => $form->get('form-actions')->get('reply')->getValue(), + ], + ), + ); + + if ($response->isOk()) { + $this->flashMessengerHelper->addSuccessMessage('Reply submitted successfully'); + return $this->redirect()->toRoute('conversations/view', $this->params()->fromRoute()); + } + + $this->handleErrors($response->getResult()); + + return parent::indexAction(); + } +} diff --git a/app/selfserve/module/Olcs/src/Controller/Factory/ConversationsControllerFactory.php b/app/selfserve/module/Olcs/src/Controller/Factory/ConversationsControllerFactory.php new file mode 100644 index 0000000000..c4f4a45b37 --- /dev/null +++ b/app/selfserve/module/Olcs/src/Controller/Factory/ConversationsControllerFactory.php @@ -0,0 +1,37 @@ +get(NiTextTranslation::class); + $authService = $container->get(AuthorizationService::class); + $flashMessengerHelper = $container->get(FlashMessengerHelperService::class); + $tableFactory = $container->get(TableFactory::class); + $formHelperService = $container->get(FormHelperService::class); + + return new ConversationsController( + $niTextTranslationUtil, + $authService, + $flashMessengerHelper, + $tableFactory, + $formHelperService, + ); + } +} diff --git a/app/selfserve/module/Olcs/src/Controller/Initializer/Navigation.php b/app/selfserve/module/Olcs/src/Controller/Initializer/Navigation.php index 5a7ea2979e..ab90c9b857 100644 --- a/app/selfserve/module/Olcs/src/Controller/Initializer/Navigation.php +++ b/app/selfserve/module/Olcs/src/Controller/Initializer/Navigation.php @@ -1,4 +1,5 @@ shouldShowPermitsTab($e); $this->togglePermitsMenus($shouldShowPermitsTab); + + $shouldShowMessagesTab = $this->shouldShowMessagesTab(); + $this->toggleMessagesTab($shouldShowMessagesTab); } /** @@ -153,6 +157,39 @@ private function referedFromGovUkPermits(MvcEvent $e): bool return in_array($referer->getUri(), $this->govUkReferers); } + /** + * Toggle Messaging menus + * + * @param bool $shouldShowMessagesTab whether to show messages tab + * + * @return void + */ + private function toggleMessagesTab(bool $shouldShowMessagesTab): void + { + $this->navigation->findBy('id', 'dashboard-messaging') + ->setVisible($shouldShowMessagesTab); + } + + private function shouldShowMessagesTab(): bool + { + $messagingToggleEnabled = $this->querySender->featuresEnabled([FeatureToggle::MESSAGING]); + + $userData = $this->identity->getUserData(); + + $hasOrganisationSubmittedLicenceApplication = $userData['hasOrganisationSubmittedLicenceApplication']; + + $isMessagingEnabled = false; + if (isset($userData['organisationUsers'][0]['organisation']['isMessagingDisabled'])) { + $isMessagingEnabled = $userData['organisationUsers'][0]['organisation']['isMessagingDisabled'] === false; + } + + return ( + $messagingToggleEnabled && + $hasOrganisationSubmittedLicenceApplication && + $isMessagingEnabled + ); + } + /** * For the benefit of unit testing * diff --git a/app/selfserve/module/Olcs/src/Controller/Listener/NavigationFactory.php b/app/selfserve/module/Olcs/src/Controller/Listener/NavigationFactory.php index 82fd2db1a3..d6005767be 100644 --- a/app/selfserve/module/Olcs/src/Controller/Listener/NavigationFactory.php +++ b/app/selfserve/module/Olcs/src/Controller/Listener/NavigationFactory.php @@ -16,7 +16,7 @@ class NavigationFactory implements FactoryInterface * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) : Navigation + public function __invoke(ContainerInterface $container, $requestedName, array $options = null): Navigation { return new Navigation( $container->get('navigation'), diff --git a/app/selfserve/module/Olcs/src/Form/Model/Fieldset/Message/Create.php b/app/selfserve/module/Olcs/src/Form/Model/Fieldset/Message/Create.php new file mode 100644 index 0000000000..9ec239155e --- /dev/null +++ b/app/selfserve/module/Olcs/src/Form/Model/Fieldset/Message/Create.php @@ -0,0 +1,81 @@ +getData('licapp'); + + if (count($data) !== 0) { + return $data; + } + + $response = $this->handleQuery( + TransferQry\Messaging\ApplicationLicenceList\ByOrganisation::create([]) + ); + + if (!$response->isOk()) { + throw new DataServiceException('Unknown Error - ' . json_encode($response)); + } + + $result = $response->getResult(); + + $this->setData('licapp', ($result['results'] ?? null)); + + return $this->getData('licapp'); + } + + public function formatDataForGroups(array $data): array + { + $optionData = [ + [ + 'label' => 'Licence', + 'options' => [] + ], + [ + 'label' => 'Application', + 'options' => [] + ] + ]; + + $optionData[0]['options'] = $data['licences']; + $optionData[1]['options'] = $data['applications']; + + $this->prefixArrayKey($optionData[0]['options'], static::PREFIX_LICENCE); + $this->prefixArrayKey($optionData[1]['options'], static::PREFIX_APPLICATION); + + return $optionData; + } + + private function prefixArrayKey(array &$array, string $prefix): void + { + foreach ($array as $k => $v) + { + $array[$prefix . $k] = $v; + unset($array[$k]); + } + } +} diff --git a/app/selfserve/module/Olcs/src/Table/Tables/messages-view.table.php b/app/selfserve/module/Olcs/src/Table/Tables/messages-view.table.php new file mode 100644 index 0000000000..adde0edad7 --- /dev/null +++ b/app/selfserve/module/Olcs/src/Table/Tables/messages-view.table.php @@ -0,0 +1,29 @@ + [ + 'class' => 'no-row-border-separator' + ], + 'variables' => [ + 'id' => 'messages-list-table', + 'title' => 'Messages', + 'empty_message' => 'There are no message records linked to this conversation to display' + ], + 'settings' => [ + 'paginate' => [ + 'limit' => [ + 'options' => [10, 25, 50], + ], + ], + ], + 'columns' => [ + [ + 'name' => 'id', + 'formatter' => ExternalConversationMessage::class, + ], + ], +]; diff --git a/app/selfserve/module/Olcs/src/Table/Tables/messages.table.php b/app/selfserve/module/Olcs/src/Table/Tables/messages.table.php new file mode 100644 index 0000000000..ae1c0ee2f8 --- /dev/null +++ b/app/selfserve/module/Olcs/src/Table/Tables/messages.table.php @@ -0,0 +1,39 @@ + [ + 'title' => 'dashboard-messages.table.title', + 'titleSingular' => 'dashboard-messages.table.title', + 'empty_message' => 'dashboard-messages.empty-message', + ], + 'settings' => [ + 'crud' => [ + 'formName' => 'messages', + 'actions' => [], + ], + 'paginate' => [ + 'limit' => [ + 'default' => 10, + 'options' => [10, 25, 50], + ], + ], + ], + 'attributes' => [], + 'columns' => [ + [ + 'title' => 'Subject', + 'name' => 'id', + 'formatter' => ExternalConversationLink::class, + ], + [ + 'title' => 'Status', + 'name' => 'status', + 'formatter' => ExternalConversationStatus::class, + ], + ], +]; diff --git a/app/selfserve/module/Olcs/view/messages-new.phtml b/app/selfserve/module/Olcs/view/messages-new.phtml new file mode 100644 index 0000000000..ae6ad71053 --- /dev/null +++ b/app/selfserve/module/Olcs/view/messages-new.phtml @@ -0,0 +1,33 @@ +partial( + 'partials/page-header-simple', + [ + 'pageTitle' => $this->translate('dashboard.messages.new.title'), + ], +); +?> + +