diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/PinturaModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/PinturaModal.tsx
index 91aeb61043..928f112a5d 100644
--- a/apps/admin-x-settings/src/components/settings/advanced/integrations/PinturaModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/PinturaModal.tsx
@@ -62,7 +62,7 @@ const PinturaModal = NiceModal.create(() => {
showToast({
type: 'success',
- message: `Pintura ${form} uploaded successfully`
+ title: `Pintura ${form} uploaded`
});
} catch (e) {
setUploadingState({js: false, css: false});
diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/SlackModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/SlackModal.tsx
index 6ff6d60860..f6ce113a57 100644
--- a/apps/admin-x-settings/src/components/settings/advanced/integrations/SlackModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/SlackModal.tsx
@@ -32,8 +32,8 @@ const SlackModal = NiceModal.create(() => {
if (await handleSave()) {
await testSlack(null);
showToast({
- message: 'Check your Slack channel for the test message',
- type: 'neutral'
+ title: 'Check your Slack channel for the test message',
+ type: 'info'
});
}
};
diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhookModal.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhookModal.tsx
index a657c78c45..5c9bdbc8bc 100644
--- a/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhookModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhookModal.tsx
@@ -1,9 +1,8 @@
import NiceModal, {useModal} from '@ebay/nice-modal-react';
import React from 'react';
-import toast from 'react-hot-toast';
import validator from 'validator';
import webhookEventOptions from './webhookEventOptions';
-import {Form, Modal, Select, TextField, showToast} from '@tryghost/admin-x-design-system';
+import {Form, Modal, Select, TextField} from '@tryghost/admin-x-design-system';
import {Webhook, useCreateWebhook, useEditWebhook} from '@tryghost/admin-x-framework/api/webhooks';
import {useForm, useHandleError} from '@tryghost/admin-x-framework/hooks';
@@ -59,14 +58,8 @@ const WebhookModal: React.FC
= ({webhook, integrationId}) =>
title='Add webhook'
formSheet
onOk={async () => {
- toast.remove();
if (await handleSave()) {
modal.remove();
- } else {
- showToast({
- type: 'pageError',
- message: 'Can\'t save webhook, please double check that you\'ve filled all mandatory fields.'
- });
}
}}
>
diff --git a/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhooksTable.tsx b/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhooksTable.tsx
index 83f03a502d..10d188e719 100644
--- a/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhooksTable.tsx
+++ b/apps/admin-x-settings/src/components/settings/advanced/integrations/WebhooksTable.tsx
@@ -22,7 +22,7 @@ const WebhooksTable: React.FC<{integration: Integration}> = ({integration}) => {
confirmModal?.remove();
showToast({
message: 'Webhook deleted',
- type: 'success'
+ type: 'info'
});
} catch (e) {
handleError(e);
diff --git a/apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx b/apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx
index 84b47fd409..df819ae119 100644
--- a/apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx
+++ b/apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx
@@ -32,8 +32,8 @@ const BetaFeatures: React.FC = () => {
setRedirectsUploading(true);
await uploadRedirects(file);
showToast({
- type: 'success',
- message: 'Redirects uploaded successfully'
+ title: 'Redirects uploaded',
+ type: 'success'
});
} catch (e) {
handleError(e);
@@ -58,7 +58,7 @@ const BetaFeatures: React.FC = () => {
await uploadRoutes(file);
showToast({
type: 'success',
- message: 'Routes uploaded successfully'
+ title: 'Routes uploaded'
});
} catch (e) {
handleError(e);
diff --git a/apps/admin-x-settings/src/components/settings/advanced/labs/MigrationOptions.tsx b/apps/admin-x-settings/src/components/settings/advanced/labs/MigrationOptions.tsx
index 80aa9b0d7a..57dfdfdd29 100644
--- a/apps/admin-x-settings/src/components/settings/advanced/labs/MigrationOptions.tsx
+++ b/apps/admin-x-settings/src/components/settings/advanced/labs/MigrationOptions.tsx
@@ -65,7 +65,7 @@ const MigrationOptions: React.FC = () => {
await deleteAllContent(null);
showToast({
type: 'success',
- message: 'All content deleted from database.'
+ title: 'All content deleted from database.'
});
modal?.remove();
await client.refetchQueries();
diff --git a/apps/admin-x-settings/src/components/settings/email/newsletters/AddNewsletterModal.tsx b/apps/admin-x-settings/src/components/settings/email/newsletters/AddNewsletterModal.tsx
index 7466604e0d..3b43deea91 100644
--- a/apps/admin-x-settings/src/components/settings/email/newsletters/AddNewsletterModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/email/newsletters/AddNewsletterModal.tsx
@@ -1,10 +1,9 @@
import NiceModal, {useModal} from '@ebay/nice-modal-react';
import React, {useEffect} from 'react';
-import {Form, LimitModal, Modal, TextArea, TextField, Toggle, showToast} from '@tryghost/admin-x-design-system';
+import {Form, LimitModal, Modal, TextArea, TextField, Toggle} from '@tryghost/admin-x-design-system';
import {HostLimitError, useLimiter} from '../../../../hooks/useLimiter';
import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing';
import {numberWithCommas} from '../../../../utils/helpers';
-import {toast} from 'react-hot-toast';
import {useAddNewsletter} from '@tryghost/admin-x-framework/api/newsletters';
import {useBrowseMembers} from '@tryghost/admin-x-framework/api/members';
import {useForm, useHandleError} from '@tryghost/admin-x-framework/hooks';
@@ -78,14 +77,8 @@ const AddNewsletterModal: React.FC = () => {
testId='add-newsletter-modal'
title='Create newsletter'
onOk={async () => {
- toast.remove();
if (await handleSave()) {
modal.remove();
- } else {
- showToast({
- type: 'pageError',
- message: 'Can\'t save newsletter, please double check that you\'ve filled all mandatory fields.'
- });
}
}}
>
diff --git a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx
index eb150ebec1..af79431360 100644
--- a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx
@@ -140,7 +140,7 @@ const Sidebar: React.FC<{
modal?.remove();
showToast({
type: 'success',
- message: 'Newsletter archived successfully'
+ message: 'Newsletter archived'
});
} catch (e) {
handleError(e);
@@ -173,7 +173,7 @@ const Sidebar: React.FC<{
modal?.remove();
showToast({
type: 'success',
- message: 'Newsletter reactivated successfully'
+ message: 'Newsletter reactivated'
});
}
});
@@ -528,7 +528,7 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b
showToast({
icon: 'email',
message: toastMessage,
- type: 'neutral'
+ type: 'info'
});
}
},
@@ -581,12 +581,7 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b
testId='newsletter-modal'
title='Newsletter'
onOk={async () => {
- if (!(await handleSave({fakeWhenUnchanged: true}))) {
- showToast({
- type: 'pageError',
- message: 'Can\'t save newsletter, please double check that you\'ve filled all mandatory fields.'
- });
- }
+ await handleSave({fakeWhenUnchanged: true});
}}
/>;
};
diff --git a/apps/admin-x-settings/src/components/settings/general/InviteUserModal.tsx b/apps/admin-x-settings/src/components/settings/general/InviteUserModal.tsx
index f7d4edd5f6..e6e066fb4a 100644
--- a/apps/admin-x-settings/src/components/settings/general/InviteUserModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/general/InviteUserModal.tsx
@@ -74,7 +74,7 @@ const InviteUserModal = NiceModal.create(() => {
const roles = rolesQuery.data.roles;
const assignableRoles = assignableRolesQuery.data.roles;
- let okLabel = 'Send invitation now';
+ let okLabel = 'Send invitation';
if (saveState === 'saving') {
okLabel = 'Sending...';
} else if (saveState === 'saved') {
@@ -123,7 +123,8 @@ const InviteUserModal = NiceModal.create(() => {
setSaveState('saved');
showToast({
- message: `Invitation successfully sent to ${email}`,
+ title: `Invitation sent`,
+ message: `${email}`,
type: 'success'
});
@@ -131,18 +132,19 @@ const InviteUserModal = NiceModal.create(() => {
updateRoute('staff?tab=invited');
} catch (e) {
setSaveState('error');
- let message = (Your invitation failed to send.
If the problem persists, contact support..);
+ let title = 'Failed to send invitation';
+ let message = (If the problem persists, contact support..);
if (e instanceof APIError) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let data = e.data as any; // we have unknown data types in the APIError/error classes
if (data?.errors?.[0]?.type === 'EmailError') {
- message = (Your invitation failed to send
Please check your Mailgun configuration. If the problem persists, contact support.);
+ message = (Check your Mailgun configuration.);
}
}
showToast({
+ title,
message,
- type: 'neutral',
- icon: 'warning'
+ type: 'error'
});
handleError(e, {withToast: false});
return;
@@ -178,12 +180,17 @@ const InviteUserModal = NiceModal.create(() => {
});
});
+ if (!!errors.email) {
+ okLabel = 'Retry';
+ }
+
return (
{
updateRoute('staff');
}}
cancelLabel=''
+ okColor={saveState === 'error' || !!errors.email ? 'red' : 'black'}
okLabel={okLabel}
testId='invite-user-modal'
title='Invite a new staff user'
diff --git a/apps/admin-x-settings/src/components/settings/general/PublicationLanguage.tsx b/apps/admin-x-settings/src/components/settings/general/PublicationLanguage.tsx
index fb169a627c..aca29daf85 100644
--- a/apps/admin-x-settings/src/components/settings/general/PublicationLanguage.tsx
+++ b/apps/admin-x-settings/src/components/settings/general/PublicationLanguage.tsx
@@ -13,8 +13,20 @@ const PublicationLanguage: React.FC<{ keywords: string[] }> = ({keywords}) => {
handleCancel,
updateSetting,
focusRef,
+ errors,
+ clearError,
handleEditingChange
- } = useSettingGroup();
+ } = useSettingGroup({
+ onValidate: () => {
+ if (!publicationLanguage) {
+ return {
+ publicationLanguage: 'Enter a value'
+ };
+ }
+
+ return {};
+ }
+ });
const [publicationLanguage] = getSettingValues(localSettings, ['locale']) as string[];
@@ -42,12 +54,14 @@ const PublicationLanguage: React.FC<{ keywords: string[] }> = ({keywords}) => {
const inputFields = (
clearError('password')}
/>
);
diff --git a/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx b/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx
index fdeaf91d5c..f44833c327 100644
--- a/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/general/UserDetailModal.tsx
@@ -16,7 +16,6 @@ import {HostLimitError, useLimiter} from '../../../hooks/useLimiter';
import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing';
import {User, canAccessSettings, hasAdminAccess, isAdminUser, isAuthorOrContributor, isEditorUser, isOwnerUser, useDeleteUser, useEditUser, useMakeOwner} from '@tryghost/admin-x-framework/api/users';
import {getImageUrl, useUploadImage} from '@tryghost/admin-x-framework/api/images';
-import {toast} from 'react-hot-toast';
import {useGlobalData} from '../../providers/GlobalDataProvider';
import {validateFacebookUrl, validateTwitterUrl} from '../../../utils/socialUrls';
@@ -36,7 +35,7 @@ const validators: Record) => string> = {
},
email: ({email}) => {
const valid = validator.isEmail(email || '');
- return valid ? '' : 'Please enter a valid email address';
+ return valid ? '' : 'Enter a valid email address';
},
url: ({url}) => {
const valid = !url || validator.isURL(url);
@@ -52,7 +51,7 @@ const validators: Record) => string> = {
},
website: ({website}) => {
const valid = !website || (validator.isURL(website) && website.length <= 2000);
- return valid ? '' : 'Website is not a valid url';
+ return valid ? '' : 'Enter a valid URL';
},
facebook: ({facebook}) => {
try {
@@ -192,7 +191,7 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
setFormState(() => updatedUserData);
modal?.remove();
showToast({
- message: _user.status === 'inactive' ? 'User un-suspended' : 'User suspended',
+ title: _user.status === 'inactive' ? 'User un-suspended' : 'User suspended',
type: 'success'
});
} catch (e) {
@@ -220,7 +219,7 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
mainModal?.remove();
navigateOnClose();
showToast({
- message: 'User deleted',
+ title: 'User deleted',
type: 'success'
});
} catch (e) {
@@ -241,7 +240,7 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
await makeOwner(user.id);
modal?.remove();
showToast({
- message: 'Ownership transferred',
+ title: 'Ownership transferred',
type: 'success'
});
} catch (e) {
@@ -361,14 +360,7 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
stickyFooter={true}
testId='user-detail-modal'
onOk={async () => {
- toast.remove();
-
- if (!(await handleSave({fakeWhenUnchanged: true}))) {
- showToast({
- type: 'pageError',
- message: 'Can\'t save user, please double check that you\'ve filled all mandatory fields.'
- });
- }
+ await (handleSave({fakeWhenUnchanged: true}));
}}
>
diff --git a/apps/admin-x-settings/src/components/settings/general/Users.tsx b/apps/admin-x-settings/src/components/settings/general/Users.tsx
index ed1b9486e5..aaede23f54 100644
--- a/apps/admin-x-settings/src/components/settings/general/Users.tsx
+++ b/apps/admin-x-settings/src/components/settings/general/Users.tsx
@@ -127,7 +127,8 @@ const UserInviteActions: React.FC<{invite: UserInvite}> = ({invite}) => {
setRevokeState('progress');
await deleteInvite(invite.id);
showToast({
- message: `Invitation revoked (${invite.email})`,
+ title: `Invitation revoked`,
+ message: invite.email,
type: 'success'
});
} catch (e) {
@@ -151,7 +152,8 @@ const UserInviteActions: React.FC<{invite: UserInvite}> = ({invite}) => {
roleId: invite.role_id
});
showToast({
- message: `Invitation resent! (${invite.email})`,
+ title: `Invitation resent`,
+ message: invite.email,
type: 'success'
});
} catch (e) {
diff --git a/apps/admin-x-settings/src/components/settings/general/users/ChangePasswordForm.tsx b/apps/admin-x-settings/src/components/settings/general/users/ChangePasswordForm.tsx
index 9dbb0c6615..c7570c87f2 100644
--- a/apps/admin-x-settings/src/components/settings/general/users/ChangePasswordForm.tsx
+++ b/apps/admin-x-settings/src/components/settings/general/users/ChangePasswordForm.tsx
@@ -218,8 +218,8 @@ const ChangePasswordForm: React.FC<{user: User}> = ({user}) => {
} catch (e) {
setSaveState('');
showToast({
- type: 'pageError',
- message: e instanceof ValidationError ? e.message : `Couldn't update password. Please try again.`
+ type: 'error',
+ title: e instanceof ValidationError ? e.message : `Couldn't update password. Please try again.`
});
handleError(e, {withToast: false});
}
diff --git a/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx b/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx
index 57afa44421..cac841a3fb 100644
--- a/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx
+++ b/apps/admin-x-settings/src/components/settings/general/users/ProfileBasics.tsx
@@ -5,7 +5,7 @@ import {UserDetailProps} from '../UserDetailModal';
import {hasAdminAccess} from '@tryghost/admin-x-framework/api/users';
import {useGlobalData} from '../../../providers/GlobalDataProvider';
-const BasicInputs: React.FC
= ({errors, validateField, clearError, user, setUserData}) => {
+const BasicInputs: React.FC = ({errors, clearError, user, setUserData}) => {
const {currentUser} = useGlobalData();
return (
@@ -16,9 +16,6 @@ const BasicInputs: React.FC = ({errors, validateField, clearErr
maxLength={191}
title="Full name"
value={user.name}
- onBlur={(e) => {
- validateField('name', e.target.value);
- }}
onChange={(e) => {
setUserData({...user, name: e.target.value});
}}
@@ -30,9 +27,6 @@ const BasicInputs: React.FC = ({errors, validateField, clearErr
maxLength={191}
title="Email"
value={user.email}
- onBlur={(e) => {
- validateField('email', e.target.value);
- }}
onChange={(e) => {
setUserData({...user, email: e.target.value});
}}
diff --git a/apps/admin-x-settings/src/components/settings/general/users/ProfileDetails.tsx b/apps/admin-x-settings/src/components/settings/general/users/ProfileDetails.tsx
index 0df0ac7f47..df7a0d875b 100644
--- a/apps/admin-x-settings/src/components/settings/general/users/ProfileDetails.tsx
+++ b/apps/admin-x-settings/src/components/settings/general/users/ProfileDetails.tsx
@@ -2,6 +2,7 @@ import CustomHeader from './CustomHeader';
import {SettingGroup, SettingGroupContent, TextArea, TextField} from '@tryghost/admin-x-design-system';
import {UserDetailProps} from '../UserDetailModal';
import {facebookHandleToUrl, facebookUrlToHandle, twitterHandleToUrl, twitterUrlToHandle, validateFacebookUrl, validateTwitterUrl} from '../../../../utils/socialUrls';
+
import {useState} from 'react';
export const DetailsInputs: React.FC = ({errors, clearError, validateField, user, setUserData}) => {
@@ -16,22 +17,19 @@ export const DetailsInputs: React.FC = ({errors, clearError, va
maxLength={65535}
title="Location"
value={user.location || ''}
- onBlur={(e) => {
- validateField('location', e.target.value);
- }}
onChange={(e) => {
setUserData({...user, location: e.target.value});
}}
onKeyDown={() => clearError('location')} />
{
- validateField('url', e.target.value);
- }}
+ // onBlur={(e) => {
+ // validateField('url', e.target.value);
+ // }}
onChange={(e) => {
setUserData({...user, website: e.target.value});
}}
@@ -76,9 +74,6 @@ export const DetailsInputs: React.FC = ({errors, clearError, va
maxLength={65535}
title="Bio"
value={user.bio || ''}
- onBlur={(e) => {
- validateField('bio', e.target.value);
- }}
onChange={(e) => {
setUserData({...user, bio: e.target.value});
}}
diff --git a/apps/admin-x-settings/src/components/settings/growth/offers/AddOfferModal.tsx b/apps/admin-x-settings/src/components/settings/growth/offers/AddOfferModal.tsx
index 159dba5d2c..2a6be523ce 100644
--- a/apps/admin-x-settings/src/components/settings/growth/offers/AddOfferModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/growth/offers/AddOfferModal.tsx
@@ -640,8 +640,9 @@ const AddOfferModal = () => {
if (!isErrorsEmpty) {
toast.remove();
showToast({
- type: 'pageError',
- message: 'Can\'t save offer, please double check that you\'ve filled all mandatory fields correctly'
+ title: 'Can\'t save offer',
+ type: 'info',
+ message: 'Make sure you filled all required fields'
});
return;
}
@@ -649,13 +650,7 @@ const AddOfferModal = () => {
try {
if (await handleSave({force: true})) {
return;
- } else {
- toast.remove();
- showToast({
- type: 'pageError',
- message: 'Can\'t save offer, please double check that you\'ve filled all mandatory fields correctly'
- });
- };
+ }
} catch (e) {
let message;
@@ -664,10 +659,13 @@ const AddOfferModal = () => {
}
toast.remove();
- showToast({
- type: 'pageError',
- message: message || 'Something went wrong while saving the offer, please try again'
- });
+ if (message) {
+ showToast({
+ title: 'Can\'t save offer',
+ type: 'error',
+ message: message || 'Please try again later'
+ });
+ }
}
}}
/>;
diff --git a/apps/admin-x-settings/src/components/settings/growth/offers/EditOfferModal.tsx b/apps/admin-x-settings/src/components/settings/growth/offers/EditOfferModal.tsx
index 39e4e61b1a..1ea97c102b 100644
--- a/apps/admin-x-settings/src/components/settings/growth/offers/EditOfferModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/growth/offers/EditOfferModal.tsx
@@ -67,7 +67,7 @@ const Sidebar: React.FC<{
modal?.remove();
showToast({
type: 'success',
- message: 'Offer archived successfully'
+ title: 'Offer archived'
});
updateRoute('offers/edit');
} catch (e) {
@@ -88,7 +88,7 @@ const Sidebar: React.FC<{
modal?.remove();
showToast({
type: 'success',
- message: 'Offer reactivated successfully'
+ title: 'Offer reactivated'
});
updateRoute('offers/edit');
} catch (e) {
@@ -284,12 +284,6 @@ const EditOfferModal: React.FC<{id: string}> = ({id}) => {
try {
if (await handleSave({force: true})) {
return;
- } else {
- toast.remove();
- showToast({
- type: 'pageError',
- message: 'Can\'t save offer, please double check that you\'ve filled all mandatory fields correctly'
- });
}
} catch (e) {
let message;
@@ -299,10 +293,13 @@ const EditOfferModal: React.FC<{id: string}> = ({id}) => {
}
toast.remove();
- showToast({
- type: 'pageError',
- message: message || 'Something went wrong while saving the offer, please try again'
- });
+ if (message) {
+ showToast({
+ title: 'Can\'t save offer',
+ type: 'error',
+ message: 'Please try again later'
+ });
+ }
}
}} /> : null;
};
diff --git a/apps/admin-x-settings/src/components/settings/growth/offers/OffersIndex.tsx b/apps/admin-x-settings/src/components/settings/growth/offers/OffersIndex.tsx
index 5328706e42..338cdc689e 100644
--- a/apps/admin-x-settings/src/components/settings/growth/offers/OffersIndex.tsx
+++ b/apps/admin-x-settings/src/components/settings/growth/offers/OffersIndex.tsx
@@ -212,8 +212,8 @@ export const OffersIndexModal = () => {
onClick: () => {
if (paidActiveTiers.length === 0) {
showToast({
- type: 'neutral',
- message: 'You must have an active tier to create an offer.'
+ type: 'info',
+ title: 'You must have an active tier to create an offer.'
});
} else {
updateRoute('offers/new');
diff --git a/apps/admin-x-settings/src/components/settings/growth/recommendations/AddRecommendationModal.tsx b/apps/admin-x-settings/src/components/settings/growth/recommendations/AddRecommendationModal.tsx
index 926dd8d111..ca06d45d76 100644
--- a/apps/admin-x-settings/src/components/settings/growth/recommendations/AddRecommendationModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/growth/recommendations/AddRecommendationModal.tsx
@@ -131,8 +131,8 @@ const AddRecommendationModal: React.FC = ({r
await addRecommendation(state);
modal.remove();
showToast({
- message: 'Successfully added a recommendation',
+ title: 'Recommendation added',
type: 'success'
});
trackEvent('Recommendation Added', {
@@ -38,14 +38,6 @@ const AddRecommendationModalConfirm: React.FC = ({r
onSaveError: handleError,
onValidate: (state) => {
const newErrors = validateDescriptionForm(state);
-
- if (Object.keys(newErrors).length !== 0) {
- showToast({
- type: 'pageError',
- message: 'Can\'t add recommendation, please double check that you\'ve filled all mandatory fields correctly.'
- });
- }
-
return newErrors;
}
});
@@ -119,8 +111,8 @@ const AddRecommendationModalConfirm: React.FC = ({r
await handleSave({force: true});
} catch (e) {
showToast({
- type: 'pageError',
- message: 'Something went wrong when adding this recommendation, please try again.'
+ type: 'error',
+ title: 'Something went wrong when adding this recommendation, please try again.'
});
}
}}
diff --git a/apps/admin-x-settings/src/components/settings/growth/recommendations/EditRecommendationModal.tsx b/apps/admin-x-settings/src/components/settings/growth/recommendations/EditRecommendationModal.tsx
index 2bdb282159..b31d71d772 100644
--- a/apps/admin-x-settings/src/components/settings/growth/recommendations/EditRecommendationModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/growth/recommendations/EditRecommendationModal.tsx
@@ -34,14 +34,6 @@ const EditRecommendationModal: React.FC {
const newErrors = validateDescriptionForm(state);
-
- if (Object.keys(newErrors).length !== 0) {
- showToast({
- type: 'pageError',
- message: 'Can\'t edit recommendation, please double check that you\'ve filled all mandatory fields correctly.'
- });
- }
-
return newErrors;
}
});
@@ -63,13 +55,10 @@ const EditRecommendationModal: React.FC {
testId='portal-modal'
title='Portal'
onOk={async () => {
- if (Object.values(errors).filter(Boolean).length) {
- showToast({
- type: 'pageError',
- message: 'Can\'t save settings, please double check that you\'ve filled all mandatory fields.'
- });
- } else {
+ if (!Object.values(errors).filter(Boolean).length) {
await handleSave({force: true});
}
}}
diff --git a/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx b/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx
index dde833afc9..a090fa441c 100644
--- a/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx
@@ -225,8 +225,9 @@ const Direct: React.FC<{onClose: () => void}> = ({onClose}) => {
} catch (e) {
if (e instanceof JSONError) {
showToast({
- type: 'pageError',
- message: 'Failed to save settings. Please check you copied both keys correctly.'
+ title: 'Failed to save settings',
+ type: 'error',
+ message: 'Check you copied both keys correctly'
});
return;
}
diff --git a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx
index eb2862b3b5..2fcff193d8 100644
--- a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx
@@ -9,7 +9,6 @@ import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing
import {Tier, useAddTier, useBrowseTiers, useEditTier} from '@tryghost/admin-x-framework/api/tiers';
import {currencies, currencySelectGroups, validateCurrencyAmount} from '../../../../utils/currency';
import {getSettingValues, useEditSettings} from '@tryghost/admin-x-framework/api/settings';
-import {toast} from 'react-hot-toast';
export type TierFormState = Partial> & {
trial_days: string;
@@ -155,7 +154,7 @@ const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => {
confirmModal?.remove();
showToast({
type: 'success',
- message: `Tier ${tier.active ? 'archived' : 'reactivated'} successfully`
+ title: `Tier ${tier.active ? 'archived' : 'reactivated'}`
});
}
});
@@ -195,15 +194,7 @@ const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => {
title={(tier ? (tier.active ? 'Edit tier' : 'Edit archived tier') : 'New tier')}
stickyFooter
onOk={async () => {
- toast.remove();
-
- if (!await handleSave({fakeWhenUnchanged: true})) {
- showToast({
- type: 'pageError',
- message: 'Can\'t save tier, please double check that you\'ve filled all mandatory fields.'
- });
- return;
- }
+ await handleSave({fakeWhenUnchanged: true});
}}
>
diff --git a/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx b/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx
index 2966d5e132..d95236534d 100644
--- a/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx
@@ -215,7 +215,7 @@ const AnnouncementBarModal: React.FC = () => {
onOk={async () => {
if (!(await handleSave({fakeWhenUnchanged: true}))) {
showToast({
- type: 'pageError',
+ type: 'error',
message: 'An error occurred while saving your changes. Please try again.'
});
}
diff --git a/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx b/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx
index 688f00ec91..1af12ccf68 100644
--- a/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx
@@ -169,7 +169,7 @@ const ThemeToolbar: React.FC
= ({
let title = 'Upload successful';
let prompt = <>
- {uploadedTheme.name} uploaded successfully.
+ {uploadedTheme.name} uploaded
>;
if (!uploadedTheme.active) {
@@ -184,7 +184,7 @@ const ThemeToolbar: React.FC = ({
title = `Upload successful with ${hasErrors ? 'errors' : 'warnings'}`;
prompt = <>
- The theme "{uploadedTheme.name}" was installed successfully but we detected some {hasErrors ? 'errors' : 'warnings'}.
+ The theme "{uploadedTheme.name}" was installed but we detected some {hasErrors ? 'errors' : 'warnings'}.
>;
if (!uploadedTheme.active) {
@@ -351,8 +351,9 @@ const ChangeThemeModal: React.FC = ({source, themeRef}) =
if (data?.themes[0]) {
await activateTheme(data.themes[0].name);
showToast({
+ title: 'Theme activated',
type: 'success',
- message: {data.themes[0].name} is now your active theme.
+ message: {data.themes[0].name} is now your active theme
});
}
confirmModal?.remove();
diff --git a/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx b/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx
index 642e5a9326..93c855ee36 100644
--- a/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx
+++ b/apps/admin-x-settings/src/components/settings/site/theme/AdvancedThemeSettings.tsx
@@ -52,8 +52,9 @@ const ThemeActions: React.FC = ({
try {
await activateTheme(theme.name);
showToast({
+ title: 'Theme activated',
type: 'success',
- message: {theme.name} is now your active theme.
+ message: {theme.name} is now your active theme
});
} catch (e) {
handleError(e);
diff --git a/apps/admin-x-settings/src/components/settings/site/theme/ThemeInstalledModal.tsx b/apps/admin-x-settings/src/components/settings/site/theme/ThemeInstalledModal.tsx
index 53bad6185d..c1cea32d19 100644
--- a/apps/admin-x-settings/src/components/settings/site/theme/ThemeInstalledModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/site/theme/ThemeInstalledModal.tsx
@@ -87,6 +87,7 @@ const ThemeInstalledModal: React.FC<{
const updatedTheme = resData.themes[0];
showToast({
+ title: 'Theme activated',
type: 'success',
message: {updatedTheme.name} is now your active theme.
});
diff --git a/apps/admin-x-settings/src/hooks/useSettingGroup.tsx b/apps/admin-x-settings/src/hooks/useSettingGroup.tsx
index 7d6272a8ad..8af1ca7cdd 100644
--- a/apps/admin-x-settings/src/hooks/useSettingGroup.tsx
+++ b/apps/admin-x-settings/src/hooks/useSettingGroup.tsx
@@ -2,9 +2,8 @@ import React, {useEffect, useRef, useState} from 'react';
import {ErrorMessages, OkProps, SaveHandler, SaveState, useForm, useHandleError} from '@tryghost/admin-x-framework/hooks';
import {Setting, SettingValue, useEditSettings} from '@tryghost/admin-x-framework/api/settings';
import {SiteData} from '@tryghost/admin-x-framework/api/site';
-import {showToast, useGlobalDirtyState} from '@tryghost/admin-x-design-system';
-import {toast} from 'react-hot-toast';
import {useGlobalData} from '../components/providers/GlobalDataProvider';
+import {useGlobalDirtyState} from '@tryghost/admin-x-design-system';
interface LocalSetting extends Setting {
dirty?: boolean;
@@ -107,15 +106,10 @@ const useSettingGroup = ({savingDelay, onValidate}: {savingDelay?: number; onVal
focusRef,
siteData,
handleSave: async () => {
- toast.remove();
const result = await handleSave();
if (result) {
setEditing(false);
} else {
- showToast({
- type: 'pageError',
- message: 'Can\'t save settings! One or more fields have errors, please double check that you\'ve filled all mandatory fields.'
- });
}
return result;
},
diff --git a/apps/admin-x-settings/test/acceptance/advanced/integrations/slack.test.ts b/apps/admin-x-settings/test/acceptance/advanced/integrations/slack.test.ts
index 420a9f5ee2..1e98e3fae2 100644
--- a/apps/admin-x-settings/test/acceptance/advanced/integrations/slack.test.ts
+++ b/apps/admin-x-settings/test/acceptance/advanced/integrations/slack.test.ts
@@ -100,7 +100,7 @@ test.describe('Slack integration', async () => {
await slackModal.getByLabel('Username').fill('My site');
await slackModal.getByRole('button', {name: 'Send test notification'}).click();
- await expect(page.getByTestId('toast-neutral')).toHaveText(/Check your Slack channel for the test message/);
+ await expect(page.getByTestId('toast-info')).toHaveText(/Check your Slack channel for the test message/);
expect(lastApiRequests.editSettings?.body).toEqual({
settings: [
diff --git a/apps/admin-x-settings/test/acceptance/advanced/labs.test.ts b/apps/admin-x-settings/test/acceptance/advanced/labs.test.ts
index 2b6f99d793..f7641d9560 100644
--- a/apps/admin-x-settings/test/acceptance/advanced/labs.test.ts
+++ b/apps/admin-x-settings/test/acceptance/advanced/labs.test.ts
@@ -24,7 +24,7 @@ test.describe('Labs', async () => {
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(`${__dirname}/../../utils/files/redirects.yml`);
- await expect(page.getByTestId('toast-success')).toContainText('Redirects uploaded successfully');
+ await expect(page.getByTestId('toast-success')).toContainText('Redirects uploaded');
expect(lastApiRequests.uploadRedirects).toBeTruthy();
@@ -56,7 +56,7 @@ test.describe('Labs', async () => {
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(`${__dirname}/../../utils/files/routes.yml`);
- await expect(page.getByTestId('toast-success')).toContainText('Routes uploaded successfully');
+ await expect(page.getByTestId('toast-success')).toContainText('Routes uploaded');
expect(lastApiRequests.uploadRoutes).toBeTruthy();
diff --git a/apps/admin-x-settings/test/acceptance/email/newsletters.test.ts b/apps/admin-x-settings/test/acceptance/email/newsletters.test.ts
index 9a083870fc..f2d0224603 100644
--- a/apps/admin-x-settings/test/acceptance/email/newsletters.test.ts
+++ b/apps/admin-x-settings/test/acceptance/email/newsletters.test.ts
@@ -27,7 +27,6 @@ test.describe('Newsletter settings', async () => {
const modal = page.getByTestId('add-newsletter-modal');
await modal.getByRole('button', {name: 'Create'}).click();
- await expect(page.getByTestId('toast-error')).toHaveText(/Can't save newsletter/);
await expect(modal).toHaveText(/Name is required/);
// Shouldn't be necessary, but without these Playwright doesn't click Create the second time for some reason
@@ -70,7 +69,6 @@ test.describe('Newsletter settings', async () => {
await modal.getByPlaceholder('Weekly Roundup').fill('');
await modal.getByRole('button', {name: 'Save'}).click();
- await expect(page.getByTestId('toast-error')).toHaveText(/Can't save newsletter/);
await expect(modal).toHaveText(/Name is required/);
await modal.getByPlaceholder('Weekly Roundup').fill('Updated newsletter');
@@ -116,14 +114,13 @@ test.describe('Newsletter settings', async () => {
await modal.getByLabel('Sender email').fill('not-an-email');
await modal.getByRole('button', {name: 'Save'}).click();
- await expect(page.getByTestId('toast-error')).toHaveText(/Can't save newsletter/);
await expect(modal).toHaveText(/Invalid email/);
await modal.getByLabel('Sender email').fill('test@test.com');
await modal.getByRole('button', {name: 'Save'}).click();
- await expect(page.getByTestId('toast-neutral')).toHaveCount(1);
- await expect(page.getByTestId('toast-neutral')).toHaveText(/sent a confirmation email to the new address/);
+ await expect(page.getByTestId('toast-info')).toHaveCount(1);
+ await expect(page.getByTestId('toast-info')).toHaveText(/sent a confirmation email to the new address/);
});
});
@@ -194,14 +191,13 @@ test.describe('Newsletter settings', async () => {
await replyToEmail.fill('not-an-email');
await modal.getByRole('button', {name: 'Save'}).click();
- await expect(page.getByTestId('toast-error')).toHaveText(/Can't save newsletter/);
await expect(modal).toHaveText(/Invalid email/);
await replyToEmail.fill('test@test.com');
await modal.getByRole('button', {name: 'Save'}).click();
- await expect(page.getByTestId('toast-neutral')).toHaveCount(1);
- await expect(page.getByTestId('toast-neutral')).toHaveText(/sent a confirmation email to the new address/);
+ await expect(page.getByTestId('toast-info')).toHaveCount(1);
+ await expect(page.getByTestId('toast-info')).toHaveText(/sent a confirmation email to the new address/);
});
});
@@ -241,13 +237,11 @@ test.describe('Newsletter settings', async () => {
// Error case #1: add invalid email address
await senderEmail.fill('Harry Potter');
await modal.getByRole('button', {name: 'Save'}).click();
- await expect(page.getByTestId('toast-error').first()).toHaveText(/Can't save newsletter/);
await expect(modal).toHaveText(/Invalid email/);
// Error case #2: the sender email address doesn't match the custom sending domain
await senderEmail.fill('harry@potter.com');
await modal.getByRole('button', {name: 'Save'}).click();
- await expect(page.getByTestId('toast-error').first()).toHaveText(/Can't save newsletter/);
await expect(modal).toHaveText(/Email must end with @customdomain.com/);
// But can have any address on the same domain, without verification
@@ -294,8 +288,8 @@ test.describe('Newsletter settings', async () => {
// There is a verification popup for the new reply-to address
await modal.getByRole('button', {name: 'Save'}).click();
- await expect(page.getByTestId('toast-neutral')).toHaveCount(1);
- await expect(page.getByTestId('toast-neutral')).toHaveText(/sent a confirmation email to the new address/);
+ await expect(page.getByTestId('toast-info')).toHaveCount(1);
+ await expect(page.getByTestId('toast-info')).toHaveText(/sent a confirmation email to the new address/);
});
});
});
diff --git a/apps/admin-x-settings/test/acceptance/general/users/invite.test.ts b/apps/admin-x-settings/test/acceptance/general/users/invite.test.ts
index 201e3fa709..7a05c9caa9 100644
--- a/apps/admin-x-settings/test/acceptance/general/users/invite.test.ts
+++ b/apps/admin-x-settings/test/acceptance/general/users/invite.test.ts
@@ -38,7 +38,7 @@ test.describe('User invitations', async () => {
// Validation failures
- await modal.getByRole('button', {name: 'Send invitation now'}).click();
+ await modal.getByRole('button', {name: 'Send invitation'}).click();
await expect(modal).toContainText('Please enter a valid email address');
// Reset error with keydown event
@@ -47,24 +47,24 @@ test.describe('User invitations', async () => {
await modal.getByLabel('Email address').fill('test');
await expect(modal).not.toContainText('Please enter a valid email address');
- await modal.getByRole('button', {name: 'Send invitation now'}).click();
+ await modal.getByRole('button', {name: 'Send invitation'}).click();
await expect(modal).toContainText('Please enter a valid email address');
await modal.getByLabel('Email address').fill('author@test.com');
- await modal.getByRole('button', {name: 'Send invitation now'}).click();
+ await modal.getByRole('button', {name: 'Retry'}).click();
await expect(modal).toContainText('A user with that email address already exists.');
await modal.getByLabel('Email address').fill('invitee@test.com');
- await modal.getByRole('button', {name: 'Send invitation now'}).click();
+ await modal.getByRole('button', {name: 'Retry'}).click();
await expect(modal).toContainText('A user with that email address was already invited.');
// Successful invitation
await modal.getByLabel('Email address').fill('newuser@test.com');
await modal.locator('input[value=author]').check();
- await modal.getByRole('button', {name: 'Send invitation now'}).click();
+ await modal.getByRole('button', {name: 'Retry'}).click();
- await expect(page.getByTestId('toast-success')).toHaveText(/Invitation successfully sent to newuser@test\.com/);
+ await expect(page.getByTestId('toast-success')).toHaveText(/Invitation sent/);
await section.getByRole('tab', {name: 'Invited'}).click();
@@ -103,7 +103,7 @@ test.describe('User invitations', async () => {
await listItem.getByRole('button', {name: 'Resend'}).click();
- await expect(page.getByTestId('toast-success')).toHaveText(/Invitation resent! \(invitee@test\.com\)/);
+ await expect(page.getByTestId('toast-success')).toHaveText(/Invitation resent/);
// Resending works by deleting and re-adding the invite
@@ -138,7 +138,7 @@ test.describe('User invitations', async () => {
await listItem.getByRole('button', {name: 'Revoke'}).click();
- await expect(page.getByTestId('toast-success')).toHaveText(/Invitation revoked \(invitee@test\.com\)/);
+ await expect(page.getByTestId('toast-success')).toHaveText(/Invitation revoked/);
expect(lastApiRequests.deleteInvite?.url).toMatch(new RegExp(`/invites/${responseFixtures.invites.invites[0].id}`));
});
diff --git a/apps/admin-x-settings/test/acceptance/general/users/profile.test.ts b/apps/admin-x-settings/test/acceptance/general/users/profile.test.ts
index a9cb02819b..12e0b7f85e 100644
--- a/apps/admin-x-settings/test/acceptance/general/users/profile.test.ts
+++ b/apps/admin-x-settings/test/acceptance/general/users/profile.test.ts
@@ -40,7 +40,7 @@ test.describe('User profile', async () => {
await modal.getByLabel('Email').fill('test');
await modal.getByRole('button', {name: 'Save & close'}).click();
- await expect(modal).toContainText('Please enter a valid email address');
+ await expect(modal).toContainText('Enter a valid email address');
await modal.getByLabel('Location').fill(new Array(195).join('a'));
await modal.getByRole('button', {name: 'Save & close'}).click();
@@ -51,7 +51,7 @@ test.describe('User profile', async () => {
await expect(modal).toContainText('Bio is too long');
await modal.getByLabel('Website').fill('not-a-website');
- await modal.getByLabel('Website').blur();
+ await modal.getByRole('button', {name: 'Save & close'}).click();
await expect(modal).toContainText('Enter a valid URL');
const facebookInput = modal.getByLabel('Facebook profile');
diff --git a/apps/admin-x-settings/test/acceptance/membership/offers.test.ts b/apps/admin-x-settings/test/acceptance/membership/offers.test.ts
index a9ee04cf1e..c279be2d9b 100644
--- a/apps/admin-x-settings/test/acceptance/membership/offers.test.ts
+++ b/apps/admin-x-settings/test/acceptance/membership/offers.test.ts
@@ -104,8 +104,11 @@ test.describe('Offers Modal', () => {
await modal.getByRole('button', {name: 'New offer'}).click();
const addModal = page.getByTestId('add-offer-modal');
await addModal.getByRole('button', {name: 'Publish'}).click();
-
- await expect(page.getByTestId('toast-error')).toContainText(/Can't save offer, please double check that you've filled all mandatory fields./);
+ const sidebar = addModal.getByTestId('add-offer-sidebar');
+ await expect(sidebar).toContainText(/Name is required/);
+ await expect(sidebar).toContainText(/Code is required/);
+ await expect(sidebar).toContainText(/Enter an amount greater than 0./);
+ await expect(sidebar).toContainText(/Display title is required/);
});
test('Errors if the offer code is already taken', async ({page}) => {
@@ -163,7 +166,6 @@ test.describe('Offers Modal', () => {
await modal.getByRole('button', {name: 'New offer'}).click();
const addModal = page.getByTestId('add-offer-modal');
await addModal.getByRole('button', {name: 'Publish'}).click();
- await expect(page.getByTestId('toast-error')).toContainText(/Can't save offer, please double check that you've filled all mandatory fields./);
const sidebar = addModal.getByTestId('add-offer-sidebar');
await expect(sidebar).toContainText(/Name is required/);
await expect(sidebar).toContainText(/Code is required/);
@@ -238,7 +240,6 @@ test.describe('Offers Modal', () => {
await offerUpdateModal.getByPlaceholder('black-friday').fill('');
await offerUpdateModal.getByRole('button', {name: 'Save'}).click();
- await expect(page.getByTestId('toast-error')).toContainText(/Can't save offer, please double check that you've filled all mandatory fields./);
await expect(offerUpdateModal).toContainText(/Please enter a code/);
await offerUpdateModal.getByPlaceholder('black-friday').fill('black-friday-offer');
diff --git a/apps/admin-x-settings/test/acceptance/membership/recommendations.test.ts b/apps/admin-x-settings/test/acceptance/membership/recommendations.test.ts
index 7ac6f50a82..9d2de9f7fa 100644
--- a/apps/admin-x-settings/test/acceptance/membership/recommendations.test.ts
+++ b/apps/admin-x-settings/test/acceptance/membership/recommendations.test.ts
@@ -171,7 +171,6 @@ test.describe('Recommendations', async () => {
expect(confirmation).toContainText('Your recommendation Recommendation 1 title will no longer be visible to your audience.');
await confirmation.getByRole('button', {name: 'Delete'}).click();
- await expect(page.getByTestId('toast-success')).toContainText('Successfully deleted the recommendation');
expect(lastApiRequests.deleteRecommendation).toBeTruthy();
});
diff --git a/apps/admin-x-settings/test/acceptance/membership/tiers.test.ts b/apps/admin-x-settings/test/acceptance/membership/tiers.test.ts
index 2b91953f5d..732c8737d6 100644
--- a/apps/admin-x-settings/test/acceptance/membership/tiers.test.ts
+++ b/apps/admin-x-settings/test/acceptance/membership/tiers.test.ts
@@ -20,7 +20,6 @@ test.describe('Tier settings', async () => {
await modal.getByRole('button', {name: 'Save & close'}).click();
- await expect(page.getByTestId('toast-error')).toHaveText(/Can't save tier/);
await expect(modal).toHaveText(/Enter a name for the tier/);
await expect(modal).toHaveText(/Amount must be at least \$1/);
@@ -106,7 +105,6 @@ test.describe('Tier settings', async () => {
await modal.getByLabel('Name').fill('');
await modal.getByRole('button', {name: 'Save & close'}).click();
- await expect(page.getByTestId('toast-error')).toHaveText(/Can't save tier/);
await expect(modal).toHaveText(/Enter a name for the tier/);
// Valid values
diff --git a/apps/admin-x-settings/test/acceptance/site/theme.test.ts b/apps/admin-x-settings/test/acceptance/site/theme.test.ts
index 2580a766d3..386ed97945 100644
--- a/apps/admin-x-settings/test/acceptance/site/theme.test.ts
+++ b/apps/admin-x-settings/test/acceptance/site/theme.test.ts
@@ -53,7 +53,7 @@ test.describe('Theme settings', async () => {
await modal.getByRole('button', {name: 'Install Headline'}).click();
- await expect(page.getByTestId('confirmation-modal')).toHaveText(/successfully installed/);
+ await expect(page.getByTestId('confirmation-modal')).toHaveText(/installed/);
await page.getByRole('button', {name: 'Activate'}).click();
diff --git a/ghost/admin/app/components/editor/publish-management.js b/ghost/admin/app/components/editor/publish-management.js
index 8c93cf5197..7d97a31906 100644
--- a/ghost/admin/app/components/editor/publish-management.js
+++ b/ghost/admin/app/components/editor/publish-management.js
@@ -250,7 +250,7 @@ export default class PublishManagement extends Component {
yield this.publishTask.perform({taskName: 'revertToDraftTask'});
const postType = capitalize(this.args.post.displayName);
- this.notifications.showNotification(`${postType} successfully reverted to a draft.`, {type: 'success'});
+ this.notifications.showNotification(`${postType} reverted to a draft.`, {type: 'success'});
return true;
} catch (e) {
diff --git a/ghost/admin/app/components/gh-members-no-members.js b/ghost/admin/app/components/gh-members-no-members.js
index 3302ce3abc..42692f186c 100644
--- a/ghost/admin/app/components/gh-members-no-members.js
+++ b/ghost/admin/app/components/gh-members-no-members.js
@@ -35,13 +35,13 @@ export default class GhMembersNoMembersComponent extends Component {
this.notifications.showNotification('Member added',
{
- description: 'You\'ve successfully added yourself as a member.'
+ description: 'You\'ve added yourself as a member.'
}
);
// force update the member count; this otherwise only updates every minute
yield this.membersCountCache.count({});
-
+
return member;
} catch (error) {
if (error) {
diff --git a/ghost/admin/app/components/gh-notification.hbs b/ghost/admin/app/components/gh-notification.hbs
index a3f5173667..6b95770875 100644
--- a/ghost/admin/app/components/gh-notification.hbs
+++ b/ghost/admin/app/components/gh-notification.hbs
@@ -1,30 +1,36 @@
-
- {{#if @message.icon}}
- {{svg-jar @message.icon}}
- {{else}}
- {{#if (eq @message.type "success")}}
- {{svg-jar "check-circle"}}
- {{else if (eq @message.type "error")}}
- {{svg-jar "warning-stroke"}}
- {{else if (eq @message.type "warn")}}
- {{svg-jar "warning-stroke"}}
- {{else}}
- {{svg-jar "check-circle"}}
- {{/if}}
- {{/if}}
-
- {{@message.message}}
-
+
+
+ {{#if (or @message.description @message.actions)}}
+
{{#if @message.description}}
{{@message.description}}
{{/if}}
-
{{#if @message.actions}}
{{@message.actions}}
{{/if}}
+ {{/if}}
diff --git a/ghost/admin/app/components/members/modals/logout-member.js b/ghost/admin/app/components/members/modals/logout-member.js
index 1fe9cd28db..f07de06de5 100644
--- a/ghost/admin/app/components/members/modals/logout-member.js
+++ b/ghost/admin/app/components/members/modals/logout-member.js
@@ -19,7 +19,7 @@ export default class LogoutMemberModal extends Component {
yield this.ajax.delete(url, options);
this.args.data.afterLogout?.();
- this.notifications.showNotification(`${this.member.name || this.member.email} has been successfully signed out from all devices.`, {type: 'success'});
+ this.notifications.showNotification(`${this.member.name || this.member.email} has been signed out from all devices.`, {type: 'success'});
this.args.close(true);
return true;
} catch (e) {
diff --git a/ghost/admin/app/components/modals/restore-revision.js b/ghost/admin/app/components/modals/restore-revision.js
index fcb576b2ea..4808d8a2e0 100644
--- a/ghost/admin/app/components/modals/restore-revision.js
+++ b/ghost/admin/app/components/modals/restore-revision.js
@@ -40,7 +40,7 @@ export default class RestoreRevisionModal extends Component {
updateTitle();
updateEditor();
- this.notifications.showNotification('Revision successfully restored.', {type: 'success'});
+ this.notifications.showNotification('Revision restored.', {type: 'success'});
closePostHistoryModal();
diff --git a/ghost/admin/app/components/posts-list/context-menu.js b/ghost/admin/app/components/posts-list/context-menu.js
index 3faa8bc56d..f945cfda40 100644
--- a/ghost/admin/app/components/posts-list/context-menu.js
+++ b/ghost/admin/app/components/posts-list/context-menu.js
@@ -21,28 +21,28 @@ function tpl(str, data) {
const messages = {
deleted: {
- single: '{Type} deleted successfully',
- multiple: '{count} {type}s deleted successfully'
+ single: '{Type} deleted',
+ multiple: '{count} {type}s deleted'
},
unpublished: {
- single: '{Type} successfully reverted to a draft',
- multiple: '{count} {type}s successfully reverted to drafts'
+ single: '{Type} reverted to a draft',
+ multiple: '{count} {type}s reverted to drafts'
},
accessUpdated: {
- single: '{Type} access successfully updated',
- multiple: '{Type} access successfully updated for {count} {type}s'
+ single: '{Type} access updated',
+ multiple: '{Type} access updated for {count} {type}s'
},
tagsAdded: {
- single: 'Tags added successfully',
- multiple: 'Tags added successfully to {count} {type}s'
+ single: 'Tags added',
+ multiple: 'Tags added to {count} {type}s'
},
tagAdded: {
- single: 'Tag added successfully',
- multiple: 'Tag added successfully to {count} {type}s'
+ single: 'Tag added',
+ multiple: 'Tag added to {count} {type}s'
},
duplicated: {
- single: '{Type} duplicated successfully',
- multiple: '{count} {type}s duplicated successfully'
+ single: '{Type} duplicated',
+ multiple: '{count} {type}s duplicated'
}
};
diff --git a/ghost/admin/app/controllers/lexical-editor.js b/ghost/admin/app/controllers/lexical-editor.js
index e9644d7759..a01ec62cb6 100644
--- a/ghost/admin/app/controllers/lexical-editor.js
+++ b/ghost/admin/app/controllers/lexical-editor.js
@@ -15,7 +15,6 @@ import moment from 'moment-timezone';
import {GENERIC_ERROR_MESSAGE} from '../services/notifications';
import {action, computed} from '@ember/object';
import {alias, mapBy} from '@ember/object/computed';
-import {capitalize} from '@ember/string';
import {captureMessage} from '@sentry/ember';
import {dropTask, enqueueTask, restartableTask, task, taskGroup, timeout} from 'ember-concurrency';
import {htmlSafe} from '@ember/template';
@@ -1266,7 +1265,7 @@ export default class LexicalEditorController extends Controller {
let actions, type, path;
if (status === 'published' || status === 'scheduled') {
- type = capitalize(this.get('post.displayName'));
+ type = this.get('post.displayName');
path = this.get('post.url');
actions = `View ${type}`;
}
diff --git a/ghost/admin/app/styles/app-dark.css b/ghost/admin/app/styles/app-dark.css
index b2dfb0f5d0..49d75f2094 100644
--- a/ghost/admin/app/styles/app-dark.css
+++ b/ghost/admin/app/styles/app-dark.css
@@ -1031,6 +1031,10 @@ input:focus,
background: var(--lightgrey);
}
+.gh-notification-close:hover svg {
+ stroke: #FFF;
+}
+
/* Opacity needed to display correctly in Safari */
::selection {
background: rgba(88, 101, 116, 0.99);
@@ -1415,7 +1419,7 @@ Onboarding checklist: Share publication modal */
opacity: .8;
}
-.gh-share-links li a {
+.gh-share-links li a {
border: 1px solid #394047;
}
diff --git a/ghost/admin/app/styles/components/notifications.css b/ghost/admin/app/styles/components/notifications.css
index 3231ecf949..8c008a4764 100644
--- a/ghost/admin/app/styles/components/notifications.css
+++ b/ghost/admin/app/styles/components/notifications.css
@@ -5,7 +5,7 @@
.gh-notifications {
position: absolute;
bottom: 30px;
- left: 30px;
+ left: 24px;
z-index: 7000;
display: flex;
flex-direction: column;
@@ -14,26 +14,33 @@
/* Base notification style */
.gh-notification {
position: relative;
- display: flex;
margin-top: 8px;
- padding: 4px 8px;
- width: 286px;
- background: var(--black);
- border-radius: 6px;
- box-shadow:
- 0 1.1px 2.3px rgba(0, 0, 0, 0.028),
- 0 3.8px 7.8px rgba(0, 0, 0, 0.042),
- 0 17px 35px -7px rgba(0, 0, 0, 0.11)
- ;
- color: #fff;
+ min-width: 272px;
+ max-width: 320px;
+ background: var(--white);
+ border-radius: 8px;
+ box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.28), 0px 100px 80px rgba(0, 0, 0, 0.0112458), 0px 41.7776px 33.4221px rgba(0, 0, 0, 0.0161557), 0px 22.3363px 17.869px rgba(0, 0, 0, 0.02), 0px 12.5216px 10.0172px rgba(0, 0, 0, 0.0238443), 0px 6.6501px 5.32008px rgba(0, 0, 0, 0.0287542), 0px 2.76726px 2.21381px rgba(0, 0, 0, 0.04);
+ color: var(--black);
font-size: 1.3rem;
line-height: 1.25em;
opacity: 1.0;
min-height: 44px;
}
+@media (max-width: 1240px) {
+ .gh-notification {
+ min-width: 232px;
+ }
+}
+
.gh-notification-icon {
- margin: 10px 0 0 6px;
+ margin-top: 1px;
+ line-height: 0;
+ color: #30CF43;
+}
+
+:is(.gh-notification-error, .gh-notification-warn) .gh-notification-icon {
+ color: #F50B23;
}
.gh-notification-icon svg {
@@ -41,24 +48,24 @@
height: 16px;
}
-.gh-notification-icon svg path {
- stroke-width: 1.5px;
- stroke: #fff;
-}
-
.gh-notification-content {
flex-grow: 1;
display: flex;
flex-direction: column;
- padding: 9px 15px 10px 10px;
+ padding: 16px;
border-radius: 3px;
- max-width: 215px;
+ margin-right: 28px;
}
.gh-notification-content p span {
white-space: nowrap;
}
+.gh-notification-header {
+ display: flex;
+ gap: 10px;
+}
+
.gh-notification-title {
display: block;
margin-top: 1px;
@@ -66,14 +73,18 @@
font-size: 1.4rem;
}
+.gh-notification-details {
+ margin: -18px 16px 16px 42px;
+}
+
.gh-notification p {
- margin: 6px 0 0;
+ margin: 8px 0 0;
padding: 0;
line-height: 1.35em;
}
.gh-notification a {
- color: #fff;
+ color: inherit;
text-decoration: underline;
font-weight: 400;
}
@@ -84,7 +95,7 @@
}
.gh-notification-actions {
- margin-top: 6px;
+ margin-top: 10px;
margin-bottom: 2px;
display: flex;
}
@@ -92,12 +103,17 @@
.gh-notification-actions a {
display: inline-block;
margin-right: 10px;
+ color: var(--darkgrey);
+}
+
+.gh-notification-actions a:hover {
+ color: var(--black);
}
.gh-notification-close {
position: absolute;
- top: 10px;
- right: 10px;
+ top: 14px;
+ right: 14px;
padding: 8px;
background: none;
border-radius: 999px;
@@ -109,16 +125,20 @@
.gh-notification-close svg {
height: 8px;
width: 8px;
- stroke: #fff;
+ stroke: #7C8B9A;
+}
+
+.gh-notification-close:hover svg {
+ stroke: #394047;
}
.gh-notification-close svg path {
stroke-width: 2px;
}
-.gh-notification-close:hover {
+/* .gh-notification-close:hover {
background: rgba(255, 255, 255, 0.3);
-}
+} */
.gh-notification-passive {
animation: notification-fade-in-spring, fade-out;
@@ -134,19 +154,16 @@
@keyframes notification-fade-in-spring {
0.00% {
- opacity: 0;
- transform: translateX(-232.05px);
+ transform: translateY(100%);
}
26.52% {
- opacity: 0.5;
- transform: translateX(5.90px);
+ transform: translateY(-3.90px);
}
63.26% {
- transform: translateX(-1.77px);
- opacity: 1;
+ transform: translateY(1.2px);
}
100.00% {
- transform: translateX(0px);
+ transform: translateY(0px);
}
}
@@ -338,4 +355,4 @@
.gh-update-banner a {
font-weight: 700;
color: var(--green-l2);
-}
\ No newline at end of file
+}
diff --git a/ghost/admin/public/assets/icons/check-circle-filled.svg b/ghost/admin/public/assets/icons/check-circle-filled.svg
new file mode 100644
index 0000000000..353fcc7533
--- /dev/null
+++ b/ghost/admin/public/assets/icons/check-circle-filled.svg
@@ -0,0 +1,10 @@
+
diff --git a/ghost/admin/public/assets/icons/info-circle-filled.svg b/ghost/admin/public/assets/icons/info-circle-filled.svg
new file mode 100644
index 0000000000..f9d2d0c210
--- /dev/null
+++ b/ghost/admin/public/assets/icons/info-circle-filled.svg
@@ -0,0 +1,10 @@
+
diff --git a/ghost/admin/public/assets/icons/warning-circle-filled.svg b/ghost/admin/public/assets/icons/warning-circle-filled.svg
new file mode 100644
index 0000000000..2be3dfbbc9
--- /dev/null
+++ b/ghost/admin/public/assets/icons/warning-circle-filled.svg
@@ -0,0 +1,10 @@
+