From 33d01b8d4fd6ebf24e9f011aa705c456c846956c Mon Sep 17 00:00:00 2001
From: Yassine Doghri <yassine@doghri.fr>
Date: Tue, 22 Jun 2021 17:59:33 +0000
Subject: [PATCH] fix(ux): allow for empty message upon episode publication and
 warn user on submit

- clarify distiction between the announcement post and the show notes
- change "note" occurences in UI by "post"
- show warning message explaining why the podcaster should fill the message area
- the podcaster
can choose to publish the episode with an empty message anyways
- redirect user to episode
dashboard with error message when episode publication pages are inaccessible instead of showing a
404 error
- add a cancel publication button in publish-edit form when episode is scheduled

closes #129
---
 app/Config/Routes.php                         |   9 +
 app/Controllers/Admin/EpisodeController.php   |  59 ++++++-
 app/Helpers/components_helper.php             |   4 +-
 app/Language/en/Episode.php                   |  17 +-
 app/Language/en/Podcast.php                   |   4 +-
 app/Language/fr/Episode.php                   |  17 +-
 app/Language/fr/Podcast.php                   |   4 +-
 .../ActivityPub/Models/StatusModel.php        |   2 +-
 app/Views/_assets/admin.ts                    |   2 +
 .../_assets/modules/PublishMessageWarning.ts  |  51 ++++++
 app/Views/admin/episode/publish.php           | 157 +++++++++---------
 app/Views/admin/episode/publish_edit.php      | 114 ++++++++-----
 12 files changed, 300 insertions(+), 140 deletions(-)
 create mode 100644 app/Views/_assets/modules/PublishMessageWarning.ts

diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index f93778a8da..41f8be7538 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -310,6 +310,15 @@ $routes->group(
                                     'permission:podcast-manage_publications',
                             ],
                         );
+                        $routes->get(
+                            'publish-cancel',
+                            'EpisodeController::publishCancel/$1/$2',
+                            [
+                                'as' => 'episode-publish-cancel',
+                                'filter' =>
+                                    'permission:podcast-manage_publications',
+                            ],
+                        );
                         $routes->get(
                             'unpublish',
                             'EpisodeController::unpublish/$1/$2',
diff --git a/app/Controllers/Admin/EpisodeController.php b/app/Controllers/Admin/EpisodeController.php
index 28ad1a8560..91c9a69954 100644
--- a/app/Controllers/Admin/EpisodeController.php
+++ b/app/Controllers/Admin/EpisodeController.php
@@ -388,7 +388,7 @@ class EpisodeController extends BaseController
         return redirect()->back();
     }
 
-    public function publish(): string
+    public function publish(): string | RedirectResponse
     {
         if ($this->episode->publication_status === 'not_published') {
             helper(['form']);
@@ -405,7 +405,10 @@ class EpisodeController extends BaseController
             return view('admin/episode/publish', $data);
         }
 
-        throw PageNotFoundException::forPageNotFound();
+        return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with(
+            'error',
+            lang('Episode.publish_error')
+        );
     }
 
     public function attemptPublish(): RedirectResponse
@@ -478,7 +481,7 @@ class EpisodeController extends BaseController
         return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]);
     }
 
-    public function publishEdit(): string
+    public function publishEdit(): string | RedirectResponse
     {
         if ($this->episode->publication_status === 'scheduled') {
             helper(['form']);
@@ -500,7 +503,11 @@ class EpisodeController extends BaseController
             ]);
             return view('admin/episode/publish_edit', $data);
         }
-        throw PageNotFoundException::forPageNotFound();
+
+        return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with(
+            'error',
+            lang('Episode.publish_edit_error')
+        );
     }
 
     public function attemptPublishEdit(): RedirectResponse
@@ -572,7 +579,44 @@ class EpisodeController extends BaseController
         return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]);
     }
 
-    public function unpublish(): string
+    public function publishCancel(): RedirectResponse
+    {
+        if ($this->episode->publication_status === 'scheduled') {
+            $db = db_connect();
+            $db->transStart();
+
+            $statusModel = new StatusModel();
+            $status = $statusModel
+                ->where([
+                    'actor_id' => $this->podcast->actor_id,
+                    'episode_id' => $this->episode->id,
+                ])
+                ->first();
+            $statusModel->removeStatus($status);
+
+            $this->episode->published_at = null;
+
+            $episodeModel = new EpisodeModel();
+            if (! $episodeModel->update($this->episode->id, $this->episode)) {
+                $db->transRollback();
+                return redirect()
+                    ->back()
+                    ->withInput()
+                    ->with('errors', $episodeModel->errors());
+            }
+
+            $db->transComplete();
+
+            return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]);
+        }
+
+        return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with(
+            'error',
+            lang('Episode.publish_cancel_error')
+        );
+    }
+
+    public function unpublish(): string | RedirectResponse
     {
         if ($this->episode->publication_status === 'published') {
             helper(['form']);
@@ -589,7 +633,10 @@ class EpisodeController extends BaseController
             return view('admin/episode/unpublish', $data);
         }
 
-        throw PageNotFoundException::forPageNotFound();
+        return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with(
+            'error',
+            lang('Episode.unpublish_error')
+        );
     }
 
     public function attemptUnpublish(): RedirectResponse
diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php
index 53d1f524fa..90e33641f7 100644
--- a/app/Helpers/components_helper.php
+++ b/app/Helpers/components_helper.php
@@ -86,11 +86,11 @@ if (! function_exists('button')) {
         }
 
         if ($options['iconLeft']) {
-            $label = icon($options['iconLeft'], 'mr-2') . $label;
+            $label = icon((string) $options['iconLeft'], 'mr-2') . $label;
         }
 
         if ($options['iconRight']) {
-            $label .= icon($options['iconRight'], 'ml-2');
+            $label .= icon((string) $options['iconRight'], 'ml-2');
         }
 
         if ($uri !== '') {
diff --git a/app/Language/en/Episode.php b/app/Language/en/Episode.php
index 80fe2ec76e..4cd4568873 100644
--- a/app/Language/en/Episode.php
+++ b/app/Language/en/Episode.php
@@ -27,8 +27,8 @@ return [
         other {# total shares}
     }',
     'total_statuses' => '{numberOfTotalStatuses, plural,
-        one {# note}
-        other {# total notes}
+        one {# total post}
+        other {# total posts}
     }',
     'all_podcast_episodes' => 'All podcast episodes',
     'back_to_podcast' => 'Go back to podcast',
@@ -36,6 +36,10 @@ return [
     'publish' => 'Publish',
     'publish_edit' => 'Edit publication',
     'unpublish' => 'Unpublish',
+    'publish_error' => 'Episode is already published.',
+    'publish_edit_error' => 'Episode is already published.',
+    'publish_cancel_error' => 'Episode is already published.',
+    'unpublish_error' => 'Episode is not published.',
     'delete' => 'Delete',
     'go_to_page' => 'Go to page',
     'create' => 'Add an episode',
@@ -112,9 +116,10 @@ return [
         'submit_edit' => 'Save episode',
     ],
     'publish_form' => [
-        'status' => 'Your note',
+        'back_to_episode_dashboard' => 'Back to episode dashboard',
+        'status' => 'Your announcement post',
         'status_hint' =>
-            'The message you write will be broadcasted to all your followers in the fediverse.',
+            "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.",
         'publication_date' => 'Publication date',
         'publication_method' => [
             'now' => 'Now',
@@ -126,6 +131,10 @@ return [
             'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm',
         'submit' => 'Publish',
         'submit_edit' => 'Edit publication',
+        'cancel_publication' => 'Cancel publication',
+        'message_warning' => 'You did not write a message for your announcement post!',
+        'message_warning_hint' => 'Having a message increases social engagement, resulting in a better visibility for your episode.',
+        'message_warning_submit' => 'Publish anyways',
     ],
     'unpublish_form' => [
         'disclaimer' =>
diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php
index fa9279f421..828ce4f233 100644
--- a/app/Language/en/Podcast.php
+++ b/app/Language/en/Podcast.php
@@ -224,8 +224,8 @@ return [
         other {<span class="font-semibold">#</span> followers}
     }',
     'statuses' => '{numberOfStatuses, plural,
-        one {<span class="font-semibold">#</span> note}
-        other {<span class="font-semibold">#</span> notes}
+        one {<span class="font-semibold">#</span> post}
+        other {<span class="font-semibold">#</span> posts}
     }',
     'activity' => 'Activity',
     'episodes' => 'Episodes',
diff --git a/app/Language/fr/Episode.php b/app/Language/fr/Episode.php
index 86233cfa4d..5f75b5061d 100644
--- a/app/Language/fr/Episode.php
+++ b/app/Language/fr/Episode.php
@@ -27,8 +27,8 @@ return [
         other {# partages en tout}
     }',
     'total_statuses' => '{numberOfTotalStatuses, plural,
-        one {# note}
-        other {# notes}
+        one {# message}
+        other {# messages}
     }',
     'all_podcast_episodes' => 'Tous les épisodes du podcast',
     'back_to_podcast' => 'Revenir au podcast',
@@ -36,6 +36,10 @@ return [
     'publish' => 'Publier',
     'publish_edit' => 'Modifier la publication',
     'unpublish' => 'Dépublier',
+    'publish_error' => 'L’épisode est déjà publié.',
+    'publish_edit_error' => 'L’épisode est déjà publié.',
+    'publish_cancel_error' => 'L’épisode est déjà publié.',
+    'unpublish_error' => 'L’épisode n’est pas publié.',
     'delete' => 'Supprimer',
     'go_to_page' => 'Voir',
     'create' => 'Ajouter un épisode',
@@ -115,9 +119,10 @@ return [
         'submit_edit' => 'Enregistrer l’épisode',
     ],
     'publish_form' => [
-        'status' => 'Votre note',
+        'back_to_episode_dashboard' => 'Retour au tableau de bord de l’épisode',
+        'status' => 'Votre message de publication',
         'status_hint' =>
-            'Le message que vous écrirez sera diffusé à toutes les personnes qui vous suivent dans le fédiverse.',
+            'Écrivez un message pour annoncer la publication de votre épisode. Le message sera diffusé à toutes les personnes qui vous suivent dans le fédiverse et mis en évidence sur la page d’accueil de votre podcast.',
         'publication_date' => 'Date de publication',
         'publication_date_clear' => 'Effacer la date de publication',
         'publication_date_hint' =>
@@ -132,6 +137,10 @@ return [
             'Vous pouvez planifier la sortie de l’épisode en saisissant une date de publication future. Ce champ doit être au format YYYY-MM-DD HH:mm',
         'submit' => 'Publier',
         'submit_edit' => 'Modifier la publication',
+        'cancel_publication' => 'Annuler la publication',
+        'message_warning' => 'Vous n’avez pas saisi de message pour l’annonce de votre épisode !',
+        'message_warning_hint' => 'Ajouter un message augmente l’engagement social, menant à une meilleure visibilité pour votre épisode.',
+        'message_warning_submit' => 'Publish quand même',
     ],
     'soundbites' => 'Extraits sonores',
     'soundbites_form' => [
diff --git a/app/Language/fr/Podcast.php b/app/Language/fr/Podcast.php
index c93d2893d9..b5ccb5b0eb 100644
--- a/app/Language/fr/Podcast.php
+++ b/app/Language/fr/Podcast.php
@@ -226,8 +226,8 @@ return [
         other {<span class="font-semibold">#</span> abonné·e·s}
     }',
     'notes' => '{numberOfStatuses, plural,
-        one {<span class="font-semibold">#</span> note}
-        other {<span class="font-semibold">#</span> notes}
+        one {<span class="font-semibold">#</span> message}
+        other {<span class="font-semibold">#</span> messages}
     }',
     'activity' => 'Activité',
     'episodes' => 'Épisodes',
diff --git a/app/Libraries/ActivityPub/Models/StatusModel.php b/app/Libraries/ActivityPub/Models/StatusModel.php
index 3f911babd0..941beb030d 100644
--- a/app/Libraries/ActivityPub/Models/StatusModel.php
+++ b/app/Libraries/ActivityPub/Models/StatusModel.php
@@ -81,7 +81,7 @@ class StatusModel extends UuidModel
      */
     protected $validationRules = [
         'actor_id' => 'required',
-        'message_html' => 'required_without[reblog_of_id]|max_length[500]',
+        'message_html' => 'max_length[500]',
     ];
 
     /**
diff --git a/app/Views/_assets/admin.ts b/app/Views/_assets/admin.ts
index be73152409..2580f6d5e7 100644
--- a/app/Views/_assets/admin.ts
+++ b/app/Views/_assets/admin.ts
@@ -4,6 +4,7 @@ import DateTimePicker from "./modules/DateTimePicker";
 import Dropdown from "./modules/Dropdown";
 import MarkdownEditor from "./modules/MarkdownEditor";
 import MultiSelect from "./modules/MultiSelect";
+import PublishMessageWarning from "./modules/PublishMessageWarning";
 import SidebarToggler from "./modules/SidebarToggler";
 import Slugify from "./modules/Slugify";
 import Soundbites from "./modules/Soundbites";
@@ -23,3 +24,4 @@ Time();
 Soundbites();
 Clipboard();
 ThemePicker();
+PublishMessageWarning();
diff --git a/app/Views/_assets/modules/PublishMessageWarning.ts b/app/Views/_assets/modules/PublishMessageWarning.ts
new file mode 100644
index 0000000000..fbde59f4ce
--- /dev/null
+++ b/app/Views/_assets/modules/PublishMessageWarning.ts
@@ -0,0 +1,51 @@
+const PublishMessageWarning = (): void => {
+  const publishForm: HTMLFormElement | null = document.querySelector(
+    "form[data-submit='validate-message']"
+  );
+
+  if (publishForm) {
+    const messageTextArea: HTMLTextAreaElement | null = publishForm.querySelector(
+      "[name='message']"
+    );
+    const submitButton: HTMLButtonElement | null = publishForm.querySelector(
+      "button[type='submit']"
+    );
+    const publishMessageWarning: HTMLDivElement | null = publishForm.querySelector(
+      "[id='publish-warning']"
+    );
+
+    if (
+      messageTextArea &&
+      submitButton &&
+      submitButton.dataset.btnTextWarning &&
+      submitButton.dataset.btnText &&
+      publishMessageWarning
+    ) {
+      publishForm.addEventListener("submit", (event) => {
+        if (
+          messageTextArea.value === "" &&
+          publishMessageWarning.classList.contains("hidden")
+        ) {
+          event.preventDefault();
+
+          publishMessageWarning.classList.remove("hidden");
+          messageTextArea.focus();
+          submitButton.innerText = submitButton.dataset
+            .btnTextWarning as string;
+        }
+      });
+
+      messageTextArea.addEventListener("input", () => {
+        if (
+          submitButton.innerText !== submitButton.dataset.btnText &&
+          messageTextArea.value !== ""
+        ) {
+          publishMessageWarning.classList.add("hidden");
+          submitButton.innerText = submitButton.dataset.btnText as string;
+        }
+      });
+    }
+  }
+};
+
+export default PublishMessageWarning;
diff --git a/app/Views/admin/episode/publish.php b/app/Views/admin/episode/publish.php
index d612d9037e..e1ed34bc51 100644
--- a/app/Views/admin/episode/publish.php
+++ b/app/Views/admin/episode/publish.php
@@ -11,26 +11,34 @@
 
 <?= $this->section('content') ?>
 
+<?= anchor(
+    route_to('episode-view', $podcast->id, $episode->id),
+    icon('arrow-left', 'mr-2 text-lg') . lang('Episode.publish_form.back_to_episode_dashboard'),
+    ['class' => 'inline-flex items-center font-semibold mr-4 text-sm'],
+) ?>
+
 <?= form_open(route_to('episode-publish', $podcast->id, $episode->id), [
     'method' => 'post',
-    'class' => 'flex flex-col max-w-xl items-start',
+    'class' => 'mx-auto flex flex-col max-w-xl items-start',
+    'data-submit' => 'validate-message'
 ]) ?>
 <?= csrf_field() ?>
 <?= form_hidden('client_timezone', 'UTC') ?>
 
 
 <label for="message" class="text-lg font-semibold"><?= lang(
-    'Episode.publish_form.status',
-) . hint_tooltip(lang('Episode.publish_form.status_hint'), 'ml-1') ?></label>
+                                                        'Episode.publish_form.status',
+                                                    ) ?></label>
+<small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.status_hint') ?></small>
 <div class="mb-8 overflow-hidden bg-white shadow-md rounded-xl">
     <div class="flex px-4 py-3">
         <img src="<?= $podcast->actor->avatar_image_url ?>" alt="<?= $podcast
-    ->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full"/>
+                                                                        ->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
         <p class="flex items-baseline min-w-0">
             <span class="mr-2 font-semibold truncate"><?= $podcast->actor
-                ->display_name ?></span>
+                                                            ->display_name ?></span>
             <span class="text-sm text-gray-500 truncate">@<?= $podcast->actor
-                ->username ?></span>
+                                                                ->username ?></span>
         </p>
     </div>
     <div class="px-4 mb-2">
@@ -39,17 +47,15 @@
                 'id' => 'message',
                 'name' => 'message',
                 'class' => 'form-textarea min-w-0 w-full',
-                'required' => 'required',
                 'placeholder' => 'Write your message...',
+                'autofocus' => ''
             ],
             old('message', '', false),
             ['rows' => 2],
         ) ?>
     </div>
     <div class="flex">
-        <img
-        src="<?= $episode->image->thumbnail_url ?>"
-        alt="<?= $episode->title ?>" class="w-24 h-24"/>
+        <img src="<?= $episode->image->thumbnail_url ?>" alt="<?= $episode->title ?>" class="w-24 h-24" />
         <div class="flex flex-col flex-1">
             <a href="<?= $episode->link ?>" class="flex-1 px-4 py-2 bg-gray-100">
                 <div class="flex items-baseline">
@@ -68,34 +74,32 @@
                 </div>
             </a>
             <audio controls preload="none" class="w-full mt-auto">
-                <source
-                src="<?= $episode->audio_file_url ?>"
-                type="<?= $episode->audio_file_mimetype ?>">
+                <source src="<?= $episode->audio_file_url ?>" type="<?= $episode->audio_file_mimetype ?>">
                 Your browser does not support the audio tag.
             </audio>
         </div>
     </div>
     <footer class="flex justify-around px-6 py-3">
         <span class="inline-flex items-center"><?= icon(
-            'chat',
-            'text-xl mr-1 text-gray-400',
-        ) . '0' ?></span>
+                                                    'chat',
+                                                    'text-xl mr-1 text-gray-400',
+                                                ) . '0' ?></span>
         <span class="inline-flex items-center"><?= icon(
-            'repeat',
-            'text-xl mr-1 text-gray-400',
-        ) . '0' ?></span>
+                                                    'repeat',
+                                                    'text-xl mr-1 text-gray-400',
+                                                ) . '0' ?></span>
         <span class="inline-flex items-center"><?= icon(
-            'heart',
-            'text-xl mr-1 text-gray-400',
-        ) . '0' ?></span>
+                                                    'heart',
+                                                    'text-xl mr-1 text-gray-400',
+                                                ) . '0' ?></span>
     </footer>
 </div>
 
 <?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?>
-    <legend class="text-lg font-semibold"><?= lang(
-        'Episode.publish_form.publication_date',
-    ) ?></legend>
-    <label for="now" class="inline-flex items-center">
+<legend class="text-lg font-semibold"><?= lang(
+                                            'Episode.publish_form.publication_date',
+                                        ) ?></legend>
+<label for="now" class="inline-flex items-center">
     <?= form_radio(
         [
             'id' => 'now',
@@ -105,65 +109,70 @@
         'now',
         old('publication_method') ? old('publish') === 'now' : true,
     ) ?>
-        <span class="ml-2"><?= lang(
-            'Episode.publish_form.publication_method.now',
-        ) ?></span>
-    </label>
-    <div class="inline-flex flex-wrap items-center mb-4 radio-toggler">
-        <?= form_radio(
-            [
-                'id' => 'schedule',
-                'name' => 'publication_method',
-                'class' => 'text-pine-700',
-            ],
-            'schedule',
-            old('publication_method') &&
-                old('publication_method') === 'schedule',
+    <span class="ml-2"><?= lang(
+                            'Episode.publish_form.publication_method.now',
+                        ) ?></span>
+</label>
+<div class="inline-flex flex-wrap items-center radio-toggler">
+    <?= form_radio(
+        [
+            'id' => 'schedule',
+            'name' => 'publication_method',
+            'class' => 'text-pine-700',
+        ],
+        'schedule',
+        old('publication_method') &&
+            old('publication_method') === 'schedule',
+    ) ?>
+    <label for="schedule" class="ml-2"><?= lang(
+                                            'Episode.publish_form.publication_method.schedule',
+                                        ) ?></label>
+    <div class="w-full mt-2 radio-toggler-element">
+        <?= form_label(
+            lang('Episode.publish_form.scheduled_publication_date'),
+            'scheduled_publication_date',
+            [],
+            lang('Episode.publish_form.scheduled_publication_date_hint'),
         ) ?>
-        <label for="schedule" class="ml-2"><?= lang(
-            'Episode.publish_form.publication_method.schedule',
-        ) ?></label>
-        <div class="w-full mt-2 radio-toggler-element">
-            <?= form_label(
-                lang('Episode.publish_form.scheduled_publication_date'),
-                'scheduled_publication_date',
-                [],
-                lang('Episode.publish_form.scheduled_publication_date_hint'),
-            ) ?>
-            <div class="flex mb-4" data-picker="datetime">
-                <?= form_input([
-                    'id' => 'scheduled_publication_date',
-                    'name' => 'scheduled_publication_date',
-                    'class' => 'form-input rounded-r-none flex-1',
-                    'value' => old('scheduled_publication_date', ''),
-                    'data-input' => '',
-                ]) ?>
-                <button
-                    class="p-3 border border-l-0 border-gray-500 bg-pine-100 focus:outline-none rounded-r-md hover:bg-pine-200 focus:ring"
-                    type="button"
-                    title="<?= lang(
-                        'Episode.publish_form.scheduled_publication_date_clear',
-                    ) ?>"
-                    data-clear=""><?= icon('close') ?></button>
-            </div>
+        <div class="flex" data-picker="datetime">
+            <?= form_input([
+                'id' => 'scheduled_publication_date',
+                'name' => 'scheduled_publication_date',
+                'class' => 'form-input rounded-r-none flex-1',
+                'value' => old('scheduled_publication_date', ''),
+                'data-input' => '',
+            ]) ?>
+            <button class="p-3 border border-l-0 border-gray-500 bg-pine-100 focus:outline-none rounded-r-md hover:bg-pine-200 focus:ring" type="button" title="<?= lang(
+                                                                                                                                                                    'Episode.publish_form.scheduled_publication_date_clear',
+                                                                                                                                                                ) ?>" data-clear=""><?= icon('close') ?></button>
         </div>
     </div>
+</div>
 <?= form_fieldset_close() ?>
 
-<div class="self-end">
-<?= anchor(
-    route_to('episode-view', $podcast->id, $episode->id),
-    lang('Common.cancel'),
-    ['class' => 'font-semibold mr-4'],
-) ?>
+<div id="publish-warning" class="inline-flex flex-col hidden p-4 text-black bg-yellow-300 border-2 border-yellow-900 rounded-md" role="alert">
+    <p class="flex items-baseline font-semibold">
+        <?= icon('alert', 'mr-2 text-lg flex-shrink-0') . lang(
+            'Episode.publish_form.message_warning',
+        ) ?></p>
+    <p>
+        <?= lang(
+            'Episode.publish_form.message_warning_hint',
+        ) ?>
+    </p>
+</div>
 
 <?= button(
     lang('Episode.publish_form.submit'),
     '',
     ['variant' => 'primary'],
-    ['type' => 'submit'],
+    [
+        'class' => 'self-end mt-4',
+        'type' => 'submit',
+        'data-btn-text-warning' => lang('Episode.publish_form.message_warning_submit'),
+        'data-btn-text' => lang('Episode.publish_form.submit_edit')
+    ],
 ) ?>
-</div>
 
 <?= form_close() ?>
 
diff --git a/app/Views/admin/episode/publish_edit.php b/app/Views/admin/episode/publish_edit.php
index 9c23551d54..52e23326f8 100644
--- a/app/Views/admin/episode/publish_edit.php
+++ b/app/Views/admin/episode/publish_edit.php
@@ -1,19 +1,26 @@
 <?= $this->extend('admin/_layout') ?>
 
 <?= $this->section('title') ?>
-<?= lang('Episode.publish') ?>
+<?= lang('Episode.publish_edit') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('pageTitle') ?>
-<?= lang('Episode.publish') ?>
+<?= lang('Episode.publish_edit') ?>
 <?= $this->endSection() ?>
 
 
 <?= $this->section('content') ?>
 
+<?= anchor(
+    route_to('episode-view', $podcast->id, $episode->id),
+    icon('arrow-left', 'mr-2 text-lg') . lang('Episode.publish_form.back_to_episode_dashboard'),
+    ['class' => 'inline-flex items-center font-semibold mr-4 text-sm'],
+) ?>
+
 <?= form_open(route_to('episode-publish_edit', $podcast->id, $episode->id), [
     'method' => 'post',
-    'class' => 'flex flex-col max-w-xl items-start',
+    'class' => 'mx-auto flex flex-col max-w-xl items-start',
+    'data-submit' => 'validate-message'
 ]) ?>
 <?= csrf_field() ?>
 <?= form_hidden('client_timezone', 'UTC') ?>
@@ -21,25 +28,26 @@
 
 
 <label for="message" class="text-lg font-semibold"><?= lang(
-    'Episode.publish_form.status',
-) . hint_tooltip(lang('Episode.publish_form.status_hint'), 'ml-1') ?></label>
+                                                        'Episode.publish_form.status',
+                                                    ) ?></label>
+<small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.status_hint') ?></small>
 <div class="mb-8 overflow-hidden bg-white shadow-md rounded-xl">
     <div class="flex px-4 py-3">
-    <img src="<?= $podcast->actor->avatar_image_url ?>" alt="<?= $podcast->actor
-    ->display_name ?>" class="w-12 h-12 mr-4 rounded-full"/>
+        <img src="<?= $podcast->actor->avatar_image_url ?>" alt="<?= $podcast->actor
+                                                                        ->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
         <div class="flex flex-col min-w-0">
             <p class="flex items-baseline min-w-0">
                 <span class="mr-2 font-semibold truncate"><?= $podcast->actor
-                    ->display_name ?></span>
+                                                                ->display_name ?></span>
                 <span class="text-sm text-gray-500 truncate">@<?= $podcast
-                    ->actor->username ?></span>
+                                                                    ->actor->username ?></span>
             </p>
             <time class="text-xs text-gray-500" itemprop="published" datetime="<?= $status->published_at->format(
-                DateTime::ATOM,
-            ) ?>" title="<?= $status->published_at ?>"><?= lang(
-    'Common.mediumDate',
-    [$status->published_at],
-) ?></time>
+                                                                                    DateTime::ATOM,
+                                                                                ) ?>" title="<?= $status->published_at ?>"><?= lang(
+                                                            'Common.mediumDate',
+                                                            [$status->published_at],
+                                                        ) ?></time>
         </div>
     </div>
     <div class="px-4 mb-2">
@@ -48,8 +56,8 @@
                 'id' => 'message',
                 'name' => 'message',
                 'class' => 'form-textarea',
-                'required' => 'required',
                 'placeholder' => 'Write your message...',
+                'autofocus' => ''
             ],
             old('message', $status->message, false),
             ['rows' => 2],
@@ -57,7 +65,7 @@
     </div>
     <div class="flex">
         <img src="<?= $episode->image
-            ->thumbnail_url ?>" alt="<?= $episode->title ?>" class="w-24 h-24" />
+                        ->thumbnail_url ?>" alt="<?= $episode->title ?>" class="w-24 h-24" />
         <div class="flex flex-col flex-1">
             <a href="<?= $episode->link ?>" class="flex-1 px-4 py-2 bg-gray-100">
                 <div class="flex items-baseline">
@@ -71,8 +79,8 @@
                 </div>
                 <div class="text-xs text-gray-600">
                     <time itemprop="published" datetime="<?= $episode->published_at->format(
-                        DateTime::ATOM,
-                    ) ?>" title="<?= $episode->published_at ?>">
+                                                                DateTime::ATOM,
+                                                            ) ?>" title="<?= $episode->published_at ?>">
                         <?= lang('Common.mediumDate', [
                             $episode->published_at,
                         ]) ?>
@@ -91,24 +99,24 @@
     </div>
     <footer class="flex justify-around px-6 py-3">
         <span class="inline-flex items-center"><?= icon(
-            'chat',
-            'text-xl mr-1 text-gray-400',
-        ) . '0' ?></span>
+                                                    'chat',
+                                                    'text-xl mr-1 text-gray-400',
+                                                ) . '0' ?></span>
         <span class="inline-flex items-center"><?= icon(
-            'repeat',
-            'text-xl mr-1 text-gray-400',
-        ) . '0' ?></span>
+                                                    'repeat',
+                                                    'text-xl mr-1 text-gray-400',
+                                                ) . '0' ?></span>
         <span class="inline-flex items-center"><?= icon(
-            'heart',
-            'text-xl mr-1 text-gray-400',
-        ) . '0' ?></span>
+                                                    'heart',
+                                                    'text-xl mr-1 text-gray-400',
+                                                ) . '0' ?></span>
     </footer>
 </div>
 
 <?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?>
 <legend class="text-lg font-semibold"><?= lang(
-    'Episode.publish_form.publication_date',
-) ?></legend>
+                                            'Episode.publish_form.publication_date',
+                                        ) ?></legend>
 <label for="now" class="inline-flex items-center">
     <?= form_radio(
         [
@@ -120,10 +128,10 @@
         old('publication_method') && old('publish') === 'now',
     ) ?>
     <span class="ml-2"><?= lang(
-        'Episode.publish_form.publication_method.now',
-    ) ?></span>
+                            'Episode.publish_form.publication_method.now',
+                        ) ?></span>
 </label>
-<div class="inline-flex flex-wrap items-center mb-4 radio-toggler">
+<div class="inline-flex flex-wrap items-center radio-toggler">
     <?= form_radio(
         [
             'id' => 'schedule',
@@ -136,8 +144,8 @@
             : true,
     ) ?>
     <label for="schedule" class="ml-2"><?= lang(
-        'Episode.publish_form.publication_method.schedule',
-    ) ?></label>
+                                            'Episode.publish_form.publication_method.schedule',
+                                        ) ?></label>
     <div class="w-full mt-2 radio-toggler-element">
         <?= form_label(
             lang('Episode.publish_form.scheduled_publication_date'),
@@ -145,7 +153,7 @@
             [],
             lang('Episode.publish_form.scheduled_publication_date_hint'),
         ) ?>
-        <div class="flex mb-4" data-picker="datetime">
+        <div class="flex" data-picker="datetime">
             <?= form_input([
                 'id' => 'scheduled_publication_date',
                 'name' => 'scheduled_publication_date',
@@ -157,30 +165,46 @@
                 'data-input' => '',
             ]) ?>
             <button class="p-3 border border-l-0 border-gray-500 bg-pine-100 focus:outline-none rounded-r-md hover:bg-pine-200 focus:ring" type="button" aria-label="<?= lang(
-                'Episode.publish_form.scheduled_publication_date_clear',
-            ) ?>" title="<?= lang(
-    'Episode.publish_form.scheduled_publication_date_clear',
-) ?>" data-clear=""><?= icon('close') ?></button>
+                                                                                                                                                                            'Episode.publish_form.scheduled_publication_date_clear',
+                                                                                                                                                                        ) ?>" title="<?= lang(
+                                                                                                                                                                                            'Episode.publish_form.scheduled_publication_date_clear',
+                                                                                                                                                                                        ) ?>" data-clear=""><?= icon('close') ?></button>
         </div>
     </div>
 </div>
 <?= form_fieldset_close() ?>
 
-<div class="self-end">
+<div id="publish-warning" class="inline-flex flex-col hidden p-4 text-black bg-yellow-300 border-2 border-yellow-900 rounded-md" role="alert">
+    <p class="flex items-baseline font-semibold">
+        <?= icon('alert', 'mr-2 text-lg flex-shrink-0') . lang(
+            'Episode.publish_form.message_warning',
+        ) ?></p>
+    <p>
+        <?= lang(
+            'Episode.publish_form.message_warning_hint',
+        ) ?>
+    </p>
+</div>
+
+<div class="flex items-center justify-between w-full mt-4">
     <?= anchor(
-        route_to('episode-view', $podcast->id, $episode->id),
-        lang('Common.cancel'),
-        ['class' => 'font-semibold mr-4'],
+        route_to('episode-publish-cancel', $podcast->id, $episode->id),
+        lang('Episode.publish_form.cancel_publication'),
+        ['class' => 'py-2 px-3 rounded-full bg-red-100 text-red-900 font-semibold  mr-4'],
     ) ?>
 
     <?= button(
         lang('Episode.publish_form.submit_edit'),
         '',
         ['variant' => 'primary'],
-        ['type' => 'submit'],
+        [
+            'type' => 'submit',
+            'data-btn-text-warning' => lang('Episode.publish_form.message_warning_submit'),
+            'data-btn-text' => lang('Episode.publish_form.submit_edit')
+        ],
     ) ?>
 </div>
 
 <?= form_close() ?>
 
-<?= $this->endSection() ?>
+<?= $this->endSection() ?>
\ No newline at end of file
-- 
GitLab