From 816166adc878df0c68aa4db068992f4562f8ebe1 Mon Sep 17 00:00:00 2001
From: Samuel Couillard <43917914+scouillard@users.noreply.github.com>
Date: Mon, 6 Feb 2023 09:39:51 -0500
Subject: [PATCH] Improve Toasts (#4753)

* Improve Toasts

* Improve logic

* Add SiteSettings toast messages

* esf

* Fix missing locales
---
 app/assets/locales/en.json                    | 16 +++++++++----
 .../components/recordings/RecordingRow.jsx    |  2 +-
 app/javascript/components/rooms/RoomCard.jsx  | 10 ++++----
 app/javascript/components/rooms/room/Room.jsx | 12 +++++-----
 .../room/room_settings/AccessCodeRow.jsx      |  2 +-
 .../site_settings/useDeleteBrandingImage.jsx  |  2 +-
 .../site_settings/useUpdateSiteSetting.jsx    | 24 ++++++++++++++++++-
 .../room_settings/useUpdateRoomSetting.jsx    | 15 ++++++++++--
 .../mutations/rooms/useDeleteAccessCode.jsx   |  2 +-
 9 files changed, 62 insertions(+), 23 deletions(-)

diff --git a/app/assets/locales/en.json b/app/assets/locales/en.json
index 1faa05cb..b47fbbe4 100644
--- a/app/assets/locales/en.json
+++ b/app/assets/locales/en.json
@@ -170,7 +170,6 @@
     "protected": "Protected",
     "length_in_minutes": "{{recording.length}} min.",
     "processing_recording": "Processing recording...",
-    "copied_urls": "Copied Recording Url(s)",
     "copy_recording_urls": "Copy Recording Url(s)",
     "recordings_list_empty": "You don't have any recordings yet!",
     "recordings_list_empty_description": "Recordings will appear here after you start a meeting and record it.",
@@ -364,7 +363,7 @@
         "room_updated": "The room has been updated.",
         "room_deleted": "The room has been deleted.",
         "room_shared": "The room has been shared.",
-        "room_unshared": "The room has been unshare.d",
+        "room_unshared": "The room has been unshared.",
         "recordings_synced": "The room recordings have been synchronized.",
         "room_configuration_updated": "The room configuration have been updated.",
         "room_setting_updated": "The room setting has been updated.",
@@ -374,15 +373,22 @@
         "meeting_started": "Meeting started.",
         "access_code_copied": "The access code has been copied.",
         "access_code_generated": "A new access code has been generated.",
-        "access_code_removed": "The access code has been removed."
+        "access_code_deleted": "The access code has been deleted.",
+        "copied_meeting_url": "The meeting URL has been copied. The link can be used to join the meeting."
       },
       "site_settings": {
-        "site_setting_updated": "The site setting has been updated."
+        "site_setting_updated": "The site setting has been updated.",
+        "brand_color_updated": "The brand color has been updated.",
+        "brand_image_updated": "The brand image has been updated.",
+        "brand_image_deleted": "The brand image has been deleted.",
+        "privacy_policy_updated": "The privacy policy has been updated.",
+        "terms_of_service_updated": "The terms of service have been updated."
       },
       "recording": {
         "recording_visibility_updated": "The recording visibility has been updated.",
         "recording_name_updated": "The recording name has been updated.",
-        "recording_deleted": "The recording has been deleted."
+        "recording_deleted": "The recording has been deleted.",
+        "copied_urls": "The recording URLs have been copied."
       },
       "role": {
         "role_created": "A new role has been created.",
diff --git a/app/javascript/components/recordings/RecordingRow.jsx b/app/javascript/components/recordings/RecordingRow.jsx
index e13511fe..0d137b95 100644
--- a/app/javascript/components/recordings/RecordingRow.jsx
+++ b/app/javascript/components/recordings/RecordingRow.jsx
@@ -23,7 +23,7 @@ export default function RecordingRow({
   function copyUrls() {
     const formatUrls = recording.formats.map((format) => format.url);
     navigator.clipboard.writeText(formatUrls);
-    toast.success(t('recording.copied_urls'));
+    toast.success(t('toast.success.recording.copied_urls'));
   }
 
   const visibilityAPI = useVisibilityAPI();
diff --git a/app/javascript/components/rooms/RoomCard.jsx b/app/javascript/components/rooms/RoomCard.jsx
index 7375016a..3cdf3f2d 100644
--- a/app/javascript/components/rooms/RoomCard.jsx
+++ b/app/javascript/components/rooms/RoomCard.jsx
@@ -10,17 +10,17 @@ import useStartMeeting from '../../hooks/mutations/rooms/useStartMeeting';
 import MeetingBadges from './MeetingBadges';
 import UserBoardIcon from './UserBoardIcon';
 
-function copyInvite(friendlyId) {
-  navigator.clipboard.writeText(`${window.location}/${friendlyId}/join`);
-  toast.success('Copied');
-}
-
 export default function RoomCard({ room }) {
   const { t } = useTranslation();
   const navigate = useNavigate();
   const handleClick = useCallback(() => { navigate(room.friendly_id); }, [room.friendly_id]);
   const startMeeting = useStartMeeting(room.friendly_id);
 
+  function copyInvite(friendlyId) {
+    navigator.clipboard.writeText(`${window.location}/${friendlyId}/join`);
+    toast.success(t('toast.success.room.copied_meeting_url'));
+  }
+
   return (
     <Card id="room-card" className="h-100 shadow-sm border-0">
       <Card.Body className="pb-0" onClick={handleClick}>
diff --git a/app/javascript/components/rooms/room/Room.jsx b/app/javascript/components/rooms/room/Room.jsx
index a6510a87..ca8ec8c1 100644
--- a/app/javascript/components/rooms/room/Room.jsx
+++ b/app/javascript/components/rooms/room/Room.jsx
@@ -16,11 +16,6 @@ import MeetingBadges from '../MeetingBadges';
 import SharedBadge from './SharedBadge';
 import RoomNamePlaceHolder from './RoomNamePlaceHolder';
 
-function copyInvite() {
-  navigator.clipboard.writeText(`${window.location}/join`);
-  toast.success('Copied');
-}
-
 export default function Room() {
   const { t } = useTranslation();
   const { friendlyId } = useParams();
@@ -30,6 +25,11 @@ export default function Room() {
   const startMeeting = useStartMeeting(friendlyId);
   const location = useLocation();
 
+  function copyInvite() {
+    navigator.clipboard.writeText(`${window.location}/join`);
+    toast.success(t('toast.success.room.copied_meeting_url'));
+  }
+
   // Custom logic to redirect from Rooms page to join page if this isnt the users room and they're not allowed to view it
   if (isError && error.response.status === 403) {
     return <Navigate to={`${location.pathname}/join`} />;
@@ -79,7 +79,7 @@ export default function Room() {
                 t('room.meeting.start_meeting')
               )}
             </Button>
-            <Button variant="brand-outline" className="mt-1 mx-2 float-end" onClick={copyInvite}>
+            <Button variant="brand-outline" className="mt-1 mx-2 float-end" onClick={() => copyInvite()}>
               <Square2StackIcon className="hi-s me-1" />
               { t('copy') }
             </Button>
diff --git a/app/javascript/components/rooms/room/room_settings/AccessCodeRow.jsx b/app/javascript/components/rooms/room/room_settings/AccessCodeRow.jsx
index 50e5d038..980bd2cc 100644
--- a/app/javascript/components/rooms/room/room_settings/AccessCodeRow.jsx
+++ b/app/javascript/components/rooms/room/room_settings/AccessCodeRow.jsx
@@ -16,7 +16,7 @@ export default function AccessCodeRow({
   // TODO: Samuel - Extract this into a shared helper function.
   const copyAccessCode = (copiedCode) => {
     navigator.clipboard.writeText(copiedCode);
-    toast.success(t('room.settings.access_code_copied'));
+    toast.success(t('toast.success.room.access_code_copied'));
   };
 
   if (config === 'false') {
diff --git a/app/javascript/hooks/mutations/admin/site_settings/useDeleteBrandingImage.jsx b/app/javascript/hooks/mutations/admin/site_settings/useDeleteBrandingImage.jsx
index a1839fe4..c83466fe 100644
--- a/app/javascript/hooks/mutations/admin/site_settings/useDeleteBrandingImage.jsx
+++ b/app/javascript/hooks/mutations/admin/site_settings/useDeleteBrandingImage.jsx
@@ -13,7 +13,7 @@ export default function useDeleteBrandingImage() {
       onSuccess: () => {
         queryClient.invalidateQueries(['getSiteSettings', 'BrandingImage']);
         queryClient.invalidateQueries('getSiteSettings');
-        toast.success(t('toast.success.site_settings.site_setting_updated'));
+        toast.success(t('toast.success.site_settings.brand_image_deleted'));
       },
       onError: () => {
         toast.error(t('toast.error.problem_completing_action'));
diff --git a/app/javascript/hooks/mutations/admin/site_settings/useUpdateSiteSetting.jsx b/app/javascript/hooks/mutations/admin/site_settings/useUpdateSiteSetting.jsx
index cfc0237c..7e145cd8 100644
--- a/app/javascript/hooks/mutations/admin/site_settings/useUpdateSiteSetting.jsx
+++ b/app/javascript/hooks/mutations/admin/site_settings/useUpdateSiteSetting.jsx
@@ -33,13 +33,35 @@ export default function useUpdateSiteSetting(name) {
     return axios.patch(`/admin/site_settings/${name}.json`, settings);
   };
 
+  const toastSuccess = () => {
+    switch (name) {
+      case 'PrimaryColor':
+        // Prevents 2 toasts from showing up when updating the primary color - which also updates the lighten color
+        break;
+      case 'PrimaryColorLight':
+        toast.success(t('toast.success.site_settings.brand_color_updated'));
+        break;
+      case 'BrandingImage':
+        toast.success(t('toast.success.site_settings.brand_image_updated'));
+        break;
+      case 'PrivacyPolicy':
+        toast.success(t('toast.success.site_settings.privacy_policy_updated'));
+        break;
+      case 'TermsOfService':
+        toast.success(t('toast.success.site_settings.terms_of_service_updated'));
+        break;
+      default:
+        toast.success(t('toast.success.site_settings.site_setting_updated'));
+    }
+  };
+
   return useMutation(
     uploadPresentation,
     {
       onSuccess: () => {
         queryClient.invalidateQueries(['getSiteSettings', name]);
         queryClient.invalidateQueries('getSiteSettings');
-        toast.success(t('toast.success.site_settings.site_setting_updated'));
+        toastSuccess();
       },
       onError: () => {
         toast.error(t('toast.error.problem_completing_action'));
diff --git a/app/javascript/hooks/mutations/room_settings/useUpdateRoomSetting.jsx b/app/javascript/hooks/mutations/room_settings/useUpdateRoomSetting.jsx
index 787b4601..d39fb426 100644
--- a/app/javascript/hooks/mutations/room_settings/useUpdateRoomSetting.jsx
+++ b/app/javascript/hooks/mutations/room_settings/useUpdateRoomSetting.jsx
@@ -16,12 +16,23 @@ export default function useUpdateRoomSetting(friendlyId) {
     return axios.patch(`/room_settings/${friendlyId}.json`, data);
   };
 
+  // Returns a more suiting toast message if the updated room setting is an access code
+  const toastSuccess = (variables) => {
+    if (variables.settingName === 'glModeratorAccessCode' || variables.settingName === 'glViewerAccessCode') {
+      if (variables.settingValue) {
+        return toast.success(t('toast.success.room.access_code_generated'));
+      }
+      return toast.success(t('toast.success.room.access_code_deleted'));
+    }
+    return toast.success(t('toast.success.room.room_setting_updated'));
+  };
+
   return useMutation(
     updateRoomSetting,
     {
-      onSuccess: () => {
+      onSuccess: (data, variables) => {
         queryClient.invalidateQueries('getRoomSettings');
-        toast.success(t('toast.success.room.room_setting_updated'));
+        toastSuccess(variables);
       },
       onError: () => {
         toast.error(t('toast.error.problem_completing_action'));
diff --git a/app/javascript/hooks/mutations/rooms/useDeleteAccessCode.jsx b/app/javascript/hooks/mutations/rooms/useDeleteAccessCode.jsx
index 2098fcaa..b1a03fbd 100644
--- a/app/javascript/hooks/mutations/rooms/useDeleteAccessCode.jsx
+++ b/app/javascript/hooks/mutations/rooms/useDeleteAccessCode.jsx
@@ -12,7 +12,7 @@ export default function useDeleteAccessCode(friendlyId) {
     {
       onSuccess: () => {
         queryClient.invalidateQueries('getAccessCodes');
-        toast.success(t('toast.success.access_code_removed'));
+        toast.success(t('toast.success.access_code_deleted'));
       },
       onError: () => {
         toast.error(t('toast.error.problem_completing_action'));
-- 
GitLab