Skip to content

Commit 45f98b4

Browse files
authored
🎨 [Frontend] Wording: Don't say Failed, say Unsuccessful (#7361)
1 parent 5e11322 commit 45f98b4

File tree

94 files changed

+507
-579
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+507
-579
lines changed

docs/messages-guidelines.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Error and Warning Message Guidelines
2+
3+
These guidelines ensure that messages are user-friendly, clear, and helpful while maintaining a professional tone. 🚀
4+
5+
Some details:
6+
7+
- Originated from [guidelines](https://wiki.speag.com/projects/SuperMash/wiki/Concepts/GUI) by @eofli and refined iterating with AI
8+
- Here’s the fully expanded and rewritten list of **error and warning message guidelines**, each with:
9+
- A **guideline**
10+
- A **rationale**
11+
- A ❌ **bad example**
12+
- A ✅ **good example**
13+
- A **reference**
14+
- This list is intended to be short enough to be read and understood for humans as well as complete so that it can be used as context for automatic correction of error/warning messages
15+
16+
---
17+
18+
## 1. Be Clear and Concise
19+
20+
- **Guideline:** Use straightforward language to describe the issue without unnecessary words.
21+
- **Rationale:** Users can quickly understand the problem and take corrective action when messages are simple and to the point.
22+
-**Bad Example:**
23+
`"An error has occurred due to an unexpected input that couldn't be parsed correctly."`
24+
-**Good Example:**
25+
`"We couldn't process your request. Please check your input and try again."`
26+
- **[Reference](https://uxwritinghub.com/error-message-examples/)**
27+
28+
---
29+
30+
## 2. Provide Specific and Actionable Information
31+
32+
- **Guideline:** Clearly state what went wrong and how the user can fix it.
33+
- **Rationale:** Specific guidance helps users resolve issues efficiently, reducing frustration.
34+
-**Bad Example:**
35+
`"Something went wrong."`
36+
-**Good Example:**
37+
`"Your session has expired. Please log in again to continue."`
38+
- **[Reference](https://www.nngroup.com/articles/error-message-guidelines/)**
39+
40+
---
41+
42+
## 3. Avoid Technical Jargon
43+
44+
- **Guideline:** Use plain language instead of technical terms or codes.
45+
- **Rationale:** Non-technical users may not understand complex terminology, hindering their ability to resolve the issue.
46+
-**Bad Example:**
47+
`"Error 429: Too many requests per second."`
48+
-**Good Example:**
49+
`"You’ve made too many requests. Please wait a moment and try again."`
50+
- **[Reference](https://cxl.com/blog/error-messages/)**
51+
52+
---
53+
54+
## 4. Use a Polite and Non-Blaming Tone
55+
56+
- **Guideline:** Frame messages in a way that doesn't place blame on the user.
57+
- **Rationale:** A respectful tone maintains a positive user experience and encourages users to continue using the application.
58+
-**Bad Example:**
59+
`"You entered the wrong password."`
60+
-**Good Example:**
61+
`"The password doesn't match. Please try again."`
62+
- **[Reference](https://atlassian.design/content/writing-guidelines/writing-error-messages/)**
63+
64+
---
65+
66+
## 5. Avoid Negative Words and Phrases
67+
68+
- **Guideline:** Steer clear of words like "error," "failed," "invalid," or "illegal."
69+
- **Rationale:** Positive language reduces user anxiety and creates a more supportive experience.
70+
-**Bad Example:**
71+
`"Invalid email address."`
72+
-**Good Example:**
73+
`"The email address format doesn't look correct. Please check and try again."`
74+
- **[Reference](https://atlassian.design/content/writing-guidelines/writing-error-messages/)**
75+
76+
---
77+
78+
## 6. Place Messages Appropriately
79+
80+
- **Guideline:** Display error messages near the relevant input field or in a clear, noticeable location.
81+
- **Rationale:** Proper placement ensures users notice the message and understand where the issue occurred.
82+
-**Bad Example:**
83+
Showing a generic "Form submission failed" message at the top of the page.
84+
-**Good Example:**
85+
Placing "Please enter a valid phone number" directly below the phone input field.
86+
- **[Reference](https://www.smashingmagazine.com/2022/08/error-messages-ux-design/)**
87+
88+
---
89+
90+
## 7. Use Inline Validation When Possible
91+
92+
- **Guideline:** Provide real-time feedback as users interact with input fields.
93+
- **Rationale:** Inline validation allows users to correct errors immediately, enhancing the flow and efficiency of the interaction.
94+
-**Bad Example:**
95+
Waiting until form submission to show all validation errors.
96+
-**Good Example:**
97+
Displaying "Password must be at least 8 characters" while the user types.
98+
- **[Reference](https://cxl.com/blog/error-messages/)**
99+
100+
---
101+
102+
## 8. Avoid Using All-Caps and Excessive Punctuation
103+
104+
- **Guideline:** Refrain from writing messages in all capital letters or using multiple exclamation marks.
105+
- **Rationale:** All-caps and excessive punctuation can be perceived as shouting, which may frustrate users.
106+
-**Bad Example:**
107+
`"INVALID INPUT!!!"`
108+
-**Good Example:**
109+
`"This input doesn't look correct. Please check and try again."`
110+
- **[Reference](https://uxwritinghub.com/error-message-examples/)**
111+
112+
---
113+
114+
## 9. Use Humor Sparingly
115+
116+
- **Guideline:** Incorporate light-hearted language only when appropriate and aligned with the application's tone.
117+
- **Rationale:** While humor can ease tension, it may not be suitable for all users or situations and can sometimes be misinterpreted.
118+
-**Bad Example:**
119+
`"Oopsie daisy! You broke something!"`
120+
-**Good Example:**
121+
`"Something went wrong. Try again, or contact support if the issue continues."`
122+
- **[Reference](https://cxl.com/blog/error-messages/)**
123+
124+
---
125+
126+
## 10. Offer Alternative Solutions or Support
127+
128+
- **Guideline:** If the user cannot resolve the issue independently, provide a way to contact support or access help resources.
129+
- **Rationale:** Offering support options ensures users don't feel stranded and can seek help to resolve their issues.
130+
-**Bad Example:**
131+
`"Access denied."`
132+
-**Good Example:**
133+
`"You don't have permission to view this page. Contact support if you think this is a mistake."`
134+
- **[Reference](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-error-handling-guidelines/)**

services/static-webserver/client/source/class/osparc/Application.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -460,15 +460,15 @@ qx.Class.define("osparc.Application", {
460460

461461
if (osparc.auth.Data.getInstance().isGuest()) {
462462
const msg = osparc.utils.Utils.createAccountMessage();
463-
osparc.FlashMessenger.getInstance().logAs(msg, "WARNING");
463+
osparc.FlashMessenger.logAs(msg, "WARNING");
464464
} else if (profile["expirationDate"]) {
465465
const now = new Date();
466466
const today = new Date(now.toISOString().slice(0, 10));
467467
const expirationDay = new Date(profile["expirationDate"]);
468468
const daysToExpiration = osparc.utils.Utils.daysBetween(today, expirationDay);
469469
if (daysToExpiration < 7) {
470470
const msg = osparc.utils.Utils.expirationMessage(daysToExpiration);
471-
osparc.FlashMessenger.getInstance().logAs(msg, "WARNING");
471+
osparc.FlashMessenger.logAs(msg, "WARNING");
472472
}
473473
}
474474

@@ -561,9 +561,9 @@ qx.Class.define("osparc.Application", {
561561

562562
__loggedOut: function(forcedReason) {
563563
if (forcedReason) {
564-
osparc.FlashMessenger.getInstance().logAs(forcedReason, "WARNING", 0);
564+
osparc.FlashMessenger.logAs(forcedReason, "WARNING", 0);
565565
} else {
566-
osparc.FlashMessenger.getInstance().logAs(this.tr("You are logged out"), "INFO");
566+
osparc.FlashMessenger.logAs(this.tr("You are logged out"), "INFO");
567567
}
568568
this.__closeAllAndToLoginPage();
569569
},

services/static-webserver/client/source/class/osparc/CookieExpirationTracker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ qx.Class.define("osparc.CookieExpirationTracker", {
103103
const timeout = osparc.utils.Utils.formatSeconds(timeoutSec);
104104
const text = qx.locale.Manager.tr(`Your session will expire in ${timeout}.<br>Please log out and log in again.`);
105105
if (this.__message === null) {
106-
this.__message = osparc.FlashMessenger.getInstance().logAs(text, "WARNING", timeoutSec*1000);
106+
this.__message = osparc.FlashMessenger.logAs(text, "WARNING", timeoutSec*1000);
107107
this.__message.getChildControl("closebutton").exclude();
108108
} else {
109109
this.__message.setMessage(text);

services/static-webserver/client/source/class/osparc/FlashMessenger.js

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
* Here is a little example of how to use the class.
2929
*
3030
* <pre class='javascript'>
31-
* osparc.FlashMessenger.getInstance().logAs(log);
31+
* osparc.FlashMessenger.logAs(log);
3232
* </pre>
3333
*/
3434

@@ -55,9 +55,33 @@ qx.Class.define("osparc.FlashMessenger", {
5555

5656
statics: {
5757
MAX_DISPLAYED: 3,
58+
59+
extractMessage: function(input, defaultMessage = "") {
60+
if (input) {
61+
if (typeof input === "string") {
62+
return input;
63+
} else if (osparc.utils.Utils.isObject(input) && "message" in input) {
64+
if (typeof input["message"] === "string") {
65+
return input["message"];
66+
} else if (osparc.utils.Utils.isObject(input["message"]) && "message" in input["message"] && typeof input["message"]["message"] === "string") {
67+
return input["message"]["message"];
68+
}
69+
}
70+
}
71+
return defaultMessage;
72+
},
73+
5874
logAs: function(message, level, duration) {
5975
return this.getInstance().logAs(message, level, duration);
60-
}
76+
},
77+
78+
logError: function(error, defaultMessage = qx.locale.Manager.tr("Oops... something went wrong"), duration = null) {
79+
if (error) {
80+
console.error(error);
81+
}
82+
const msg = this.extractMessage(error, defaultMessage);
83+
return this.getInstance().logAs(msg, "ERROR", duration);
84+
},
6185
},
6286

6387
members: {
@@ -68,7 +92,7 @@ qx.Class.define("osparc.FlashMessenger", {
6892
/**
6993
* Public function to log a FlashMessage to the user.
7094
*
71-
* @param {String} message Message that the message will show.
95+
* @param {String || Object} message Message (or Object containing the message) that the message will show.
7296
* @param {String="INFO","DEBUG","WARNING","ERROR"} level Level of the warning. The color of the badge will change accordingly.
7397
* @param {Number} duration
7498
*/
@@ -81,9 +105,7 @@ qx.Class.define("osparc.FlashMessenger", {
81105
},
82106

83107
log: function(logMessage) {
84-
const message = osparc.utils.Utils.isObject(logMessage.message) && "message" in logMessage.message ?
85-
logMessage.message.message :
86-
logMessage.message;
108+
const message = this.self().extractMessage(logMessage);
87109

88110
const level = logMessage.level.toUpperCase(); // "DEBUG", "INFO", "WARNING", "ERROR"
89111

services/static-webserver/client/source/class/osparc/NewUITracker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ qx.Class.define("osparc.NewUITracker", {
3636
msg += "<br>";
3737
msg += qx.locale.Manager.tr("Click the Reload button to get the latest features.");
3838
// permanent message
39-
const flashMessage = osparc.FlashMessenger.getInstance().logAs(msg, "INFO", 0).set({
39+
const flashMessage = osparc.FlashMessenger.logAs(msg, "INFO", 0).set({
4040
maxWidth: 500
4141
});
4242
const reloadButton = osparc.utils.Utils.reloadNoCacheButton();

services/static-webserver/client/source/class/osparc/Preferences.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,7 @@ qx.Class.define("osparc.Preferences", {
173173
osparc.Preferences.patchPreference(preferenceId, newValue)
174174
.then(() => preferencesSettings.set(preferenceId, newValue))
175175
.catch(err => {
176-
console.error(err);
177-
osparc.FlashMessenger.logAs(err.message, "ERROR");
176+
osparc.FlashMessenger.logError(err);
178177
preferenceField.setValue(oldValue);
179178
})
180179
.finally(() => preferenceField.setEnabled(true));
@@ -208,10 +207,7 @@ qx.Class.define("osparc.Preferences", {
208207
wallets.forEach(wallet => wallet.setPreferredWallet(wallet.getWalletId() === walletId));
209208
this.setPreferredWalletId(walletId);
210209
})
211-
.catch(err => {
212-
console.error(err);
213-
osparc.FlashMessenger.logAs(err.message, "ERROR");
214-
});
210+
.catch(err => osparc.FlashMessenger.logError(err));
215211
},
216212

217213
__patchPreference: function(value, _, propName) {

services/static-webserver/client/source/class/osparc/activityManager/ActivityManager.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,15 @@ qx.Class.define("osparc.activityManager.ActivityManager", {
119119
/*
120120
const runButton = new qx.ui.toolbar.Button(this.tr("Run"), "@FontAwesome5Solid/play/14");
121121
actionsPart.add(runButton);
122-
runButton.addListener("execute", () => osparc.FlashMessenger.getInstance().logAs("Not implemented"));
122+
runButton.addListener("execute", () => osparc.FlashMessenger.logAs("Not implemented"));
123123
124124
const stopButton = new qx.ui.toolbar.Button(this.tr("Stop"), "@FontAwesome5Solid/stop-circle/14");
125125
actionsPart.add(stopButton);
126-
stopButton.addListener("execute", () => osparc.FlashMessenger.getInstance().logAs("Not implemented"));
126+
stopButton.addListener("execute", () => osparc.FlashMessenger.logAs("Not implemented"));
127127
128128
const infoButton = new qx.ui.toolbar.Button(this.tr("Info"), "@FontAwesome5Solid/info/14");
129129
actionsPart.add(infoButton);
130-
infoButton.addListener("execute", () => osparc.FlashMessenger.getInstance().logAs("Not implemented"));
130+
infoButton.addListener("execute", () => osparc.FlashMessenger.logAs("Not implemented"));
131131
132132
[runButton, stopButton, infoButton].map(button => this.__tree.bind("selected", button, "enabled", {
133133
converter: data => data.length > 0

services/static-webserver/client/source/class/osparc/activityManager/ActivityTree.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,7 @@ qx.Class.define("osparc.activityManager.ActivityTree", {
241241
}
242242
this.fireEvent("treeUpdated");
243243
})
244-
.catch(e => {
245-
console.error(e);
246-
})
244+
.catch(err => console.error(err))
247245
.then(() => {
248246
// Give a 2 seconds delay
249247
setTimeout(() => {

services/static-webserver/client/source/class/osparc/admin/Announcements.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ qx.Class.define("osparc.admin.Announcements", {
115115
}
116116
if (widgets.length === 0) {
117117
const msg = "Select at least one widget";
118-
osparc.FlashMessenger.getInstance().logAs(msg, "WARNING");
118+
osparc.FlashMessenger.logAs(msg, "WARNING");
119119
}
120120
const announcementData = {
121121
"id": osparc.utils.Utils.uuidV4(),

services/static-webserver/client/source/class/osparc/auth/Data.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ qx.Class.define("osparc.auth.Data", {
2626

2727
properties: {
2828
/**
29-
* Basic authentification with a token
29+
* Basic authentication with a token
3030
*/
3131
auth: {
3232
init: null,

services/static-webserver/client/source/class/osparc/auth/LoginWithDecorators.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ qx.Class.define("osparc.auth.LoginWithDecorators", {
225225
pages.setSelection([resetPassword]);
226226
}
227227
} else if (urlFragment.params && urlFragment.params.registered) {
228-
osparc.FlashMessenger.getInstance().logAs(this.tr("Your account has been created.<br>You can now use your credentials to login."));
228+
osparc.FlashMessenger.logAs(this.tr("Your account has been created.<br>You can now use your credentials to login."));
229229
}
230230

231231
login.addListener("toRegister", () => {

services/static-webserver/client/source/class/osparc/auth/Manager.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,13 @@ qx.Class.define("osparc.auth.Manager", {
176176
} else {
177177
const resp = JSON.parse(xhr.responseText);
178178
if (resp.error == null) {
179-
reject(this.tr("Login failed"));
179+
reject(this.tr("Unsuccessful Login"));
180180
} else {
181181
reject(resp.error.message);
182182
}
183183
}
184184
};
185-
xhr.onerror = () => reject(this.tr("Login failed"));
185+
xhr.onerror = () => reject(this.tr("Unsuccessful Login"));
186186
xhr.open("POST", url, true);
187187
xhr.setRequestHeader("Content-Type", "application/json");
188188
xhr.send(JSON.stringify(params));

services/static-webserver/client/source/class/osparc/auth/ui/Login2FAValidationCodeView.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ qx.Class.define("osparc.auth.ui.Login2FAValidationCodeView", {
125125
});
126126
this.restartSMSButton(retryAfter);
127127
})
128-
.catch(err => osparc.FlashMessenger.logAs(err.message, "ERROR"))
128+
.catch(err => osparc.FlashMessenger.logError(err))
129129
.finally(() => resendCodeSMSBtn.setFetching(false));
130130
}, this);
131131

@@ -145,7 +145,7 @@ qx.Class.define("osparc.auth.ui.Login2FAValidationCodeView", {
145145
});
146146
this.restartEmailButton(retryAfter);
147147
})
148-
.catch(err => osparc.FlashMessenger.logAs(err.message, "ERROR"))
148+
.catch(err => osparc.FlashMessenger.logError(err))
149149
.finally(() => resendCodeEmailBtn.setFetching(false));
150150
}, this);
151151
this.add(resendLayout);
@@ -188,7 +188,7 @@ qx.Class.define("osparc.auth.ui.Login2FAValidationCodeView", {
188188
msg = String(msg) || this.tr("Invalid code");
189189
validationCodeTF.setInvalidMessage(msg);
190190

191-
osparc.FlashMessenger.getInstance().logAs(msg, "ERROR");
191+
osparc.FlashMessenger.logError(msg);
192192
};
193193

194194
if (this._form.validate()) {

services/static-webserver/client/source/class/osparc/auth/ui/LoginView.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ qx.Class.define("osparc.auth.ui.LoginView", {
235235

236236
const twoFactorAuthCbk = (nextStep, message, retryAfter) => {
237237
this.__loginBtn.setFetching(false);
238-
osparc.FlashMessenger.getInstance().logAs(message, "INFO");
238+
osparc.FlashMessenger.logAs(message, "INFO");
239239
this.fireDataEvent("to2FAValidationCode", {
240240
userEmail: email.getValue(),
241241
nextStep,
@@ -251,15 +251,15 @@ qx.Class.define("osparc.auth.ui.LoginView", {
251251
const failFun = msg => {
252252
this.__loginBtn.setFetching(false);
253253
// TODO: can get field info from response here
254-
msg = String(msg) || this.tr("Typed an invalid email or password");
254+
msg = String(msg) || this.tr("email or password don't look correct");
255255
[email, pass].forEach(item => {
256256
item.set({
257257
invalidMessage: msg,
258258
valid: false
259259
});
260260
});
261261

262-
osparc.FlashMessenger.getInstance().logAs(msg, "ERROR");
262+
osparc.FlashMessenger.logError(msg);
263263
};
264264

265265
const manager = osparc.auth.Manager.getInstance();

0 commit comments

Comments
 (0)