Skip to content

Commit

Permalink
Notification revamp (#468)
Browse files Browse the repository at this point in the history
* [CSS] Update styles

* [VUE] New alert layout

* [VUE] Update hideAlert name

* [CSS] Badge count & and pass count to Alert.vue

* [CSS] Update for info and warning icons

* Add 'action' data to Alert constructor

* Prettier

* Testing: hook some of the new functionality in to the frontend
NOT finished, and contains some testing code.

* [VUE+CSS] Update second button

* testing: revert commit used for quicker testing

* feat: add ability to run 'actions' from alerts

* Prettier

* feat: add "Open In Explorer" button to Tx sent Notifications

* tests: fix createAlert test

* Prettier

* tests: test all constructor arguments for createAlert

* Update scripts/alerts/Alert.vue

* Prettier

---------

Co-authored-by: JSKitty <mail@jskitty.cat>
Co-authored-by: Alessandro Rezzi <alessandrorezzi2000@gmail.com>
Co-authored-by: Duddino <duddino@duddino.com>
  • Loading branch information
4 people authored Nov 26, 2024
1 parent 648377a commit 720d2a9
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 46 deletions.
122 changes: 95 additions & 27 deletions assets/style/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2286,7 +2286,7 @@ a {
position: fixed;
z-index: 10000;
right: 15px;
bottom: 0px;
top: 100px;
display: flex;
flex-direction: column;
}
Expand Down Expand Up @@ -3593,57 +3593,125 @@ select.form-control option {
}
}

.notifyButtonFirst {
border-bottom-right-radius:0px!important;
margin-right:1px!important;
padding: 0px 21px;
}

.notifyButtonSecond {
border-bottom-left-radius:0px!important;
margin-left:1px!important;
width:100%;
text-transform: uppercase;
}

.btn-notification-close {
color: #D5C9EC;
border: 2px solid #8529ED;
border-radius: 0px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
margin-left: -1px;
margin-bottom: -1px;
margin-right: -1px;
background: rgb(30,20,49);
background: linear-gradient(0deg, rgba(30,20,49,1) 0%, rgba(44,16,79,1) 100%);
font-weight: 500;
}

.btn-notification-close:hover {
border: 2px solid #8529ED;
color: #c7bbdd;
}

.btn-notification-action {
color: #D5C9EC;
border-radius: 0px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
margin-left: -1px;
margin-bottom: -1px;
margin-right: -1px;
background: rgb(30,20,49);
background: linear-gradient(0deg, #741EEA 0%, #9231EE 100%);
font-weight: 500;
}

.btn-notification-action:hover {
color: #c7bbdd;
}

.notifyWrapper .notifyBadgeCount {
position: absolute;
right: 0px;
border: 2px solid #9A21FF;
background-color: #431180;
border-radius: 100px;
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
font-size: 13px;
font-weight: 500;
}

.notifyWrapper {
opacity: 1;
z-index: 999999;
background-color: #320044;
border-radius: 5px;
display: inline-flex;
align-items: stretch;
border: 1px solid #9F00F9;
cursor: pointer;
background-color: #260a47;
border-radius: 11px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
border: 1px solid #51199f;
margin-bottom: 15px;
opacity: 0;
transition: all 0.250s ease-in-out;
}

.notifyWrapper .notifyIcon {
padding: 20px 11px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
margin-top: -1px;
margin-left: -1px;
margin-bottom: -1px;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
display: flex;
height: 45px;
margin-top: 17px;
margin-left: -5px;
width: 45px;
justify-content: center;
align-items: center;
}

.notifyWrapper .notify-warning {
background-color: #630808;
border-top: 1px solid #FF0000;
border-left: 1px solid #FF0000;
border-bottom: 1px solid #FF0000;
background: rgb(138,0,29);
background: linear-gradient(0deg, rgba(138,0,29,1) 0%, rgba(181,0,0,1) 100%);
border: 1px solid #FF1C50;
}

.notifyWrapper .notify-info {
background-color: #084363;
border-top: 1px solid #0095ff;
border-left: 1px solid #0095ff;
border-bottom: 1px solid #0095ff;
background: rgb(47,18,83);
background: linear-gradient(341deg, rgba(47,18,83,1) 0%, rgba(123,101,157,1) 100%);
border: 1px solid #906DB1;
}

.notifyWrapper .notify-success {
background-color: #1c6308;
border-top: 1px solid #1aff00;
border-left: 1px solid #1aff00;
border-bottom: 1px solid #1aff00;
background: rgb(46,82,0);
background: linear-gradient(341deg, rgba(46,82,0,1) 0%, rgba(95,168,0,1) 100%);
border: 1px solid #69BB00;
}

.notifyWrapper .notifyText {
padding-left: 11px;
padding-right: 17px;
padding-top: 14px;
padding-bottom: 14px;
padding-left: 14px;
padding-right: 40px;
padding-top: 19px;
padding-bottom: 19px;
color: #8c7aa8;
}

.notifyWrapper .notifyText b {
color:#DCD1F3;
}

.sliderStyle .arrow {
Expand Down
35 changes: 31 additions & 4 deletions scripts/alerts/Alert.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { computed, toRefs } from 'vue';
const props = defineProps({
message: String,
level: String,
notificationCount: Number,
actionName: String,
});
const { message, level } = toRefs(props);
Expand All @@ -25,14 +27,39 @@ const icon = computed(() => {
<template>
<div
class="notifyWrapper"
@click="$emit('click')"
:class="{ [level]: true }"
:style="{ opacity: 1 }"
data-testid="alert"
>
<div class="notifyIcon" :class="{ ['notify-' + level]: true }">
<i class="fas fa-xl" :class="{ [icon]: true }"> </i>
<div class="notifyBadgeCount" v-if="notificationCount > 1">
{{ notificationCount }}
</div>
<div style="display: inline-flex; align-items: stretch">
<div class="notifyIcon" :class="{ ['notify-' + level]: true }">
<i class="fas fa-xl" :class="{ [icon]: true }"> </i>
</div>
<div class="notifyText" v-html="message"></div>
</div>
<div
style="display: flex"
:style="
actionName ? 'flex-direction: row' : 'flex-direction: column'
"
>
<button
:class="actionName ? 'notifyButtonFirst' : ''"
class="btn btn-notification-close"
@click="$emit('hideAlert')"
>
CLOSE
</button>
<button
v-if="actionName"
class="btn btn-notification-action notifyButtonSecond"
@click="$emit('runAction')"
>
{{ actionName }}
</button>
</div>
<div class="notifyText" v-html="message"></div>
</div>
</template>
20 changes: 17 additions & 3 deletions scripts/alerts/Alerts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ watch(alerts, () => {
let count = 1;
const pushAlert = () => {
if (previousAlert) {
const countStr = count === 1 ? '' : ` (x${count})`;
const timeout =
previousAlert.created + previousAlert.timeout - Date.now();
const show = timeout > 0;
if (!show) return;
const alert = ref({
...previousAlert,
message: `${previousAlert.message}${countStr}`,
message: `${previousAlert.message}`,
show,
count,
actionName: previousAlert.actionName,
actionFunc: previousAlert.actionFunc,
// Store original message so we can use it as key.
// This skips the animation in case of multiple errors
original: previousAlert.message,
Expand All @@ -45,6 +47,15 @@ watch(alerts, () => {
pushAlert();
foldedAlerts.value = res;
});
/**
* Run an 'action' connected to an alert
* @param {import('./alert.js').Alert} cAlert - The caller alert which is running an action
*/
function runAction(cAlert) {
cAlert.actionFunc();
cAlert.show = false;
}
</script>
<template>
Expand All @@ -57,7 +68,10 @@ watch(alerts, () => {
<Alert
:message="alert.value.message"
:level="alert.value.level"
@click="alert.value.show = false"
:notificationCount="alert.value.count"
:actionName="alert.value.actionName"
@hideAlert="alert.value.show = false"
@runAction="runAction(alert.value)"
/>
</div>
</transition-group>
Expand Down
41 changes: 36 additions & 5 deletions scripts/alerts/alert.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,30 @@ export class Alert {
*/
created;

constructor({ message, level, timeout = 0, created = Date.now() }) {
/**
* @type{string} The user-readable button title for an Action
*/
actionName;

/**
* @type{function} The function to be executed if the user runs the Action
*/
actionFunc;

constructor({
message,
level,
timeout = 0,
created = Date.now(),
actionName,
actionFunc,
}) {
this.message = message;
this.level = level;
this.timeout = timeout;
this.created = created;
this.actionName = actionName;
this.actionFunc = actionFunc;
}
}

Expand Down Expand Up @@ -55,9 +74,13 @@ export class AlertController {
* @param {'success'|'info'|'warning'} type - The alert level
* @param {string} message - The message to relay to the user
* @param {number?} timeout - The time in `ms` until the alert expires
* @param {string?} actionName - The button title of an optional Action to perform
* @param {function?} actionFunc - The function to execute if the Action button is used
*/
createAlert(level, message, timeout = 10000) {
this.addAlert(new Alert({ level, message, timeout }));
createAlert(level, message, timeout = 10000, actionName, actionFunc) {
this.addAlert(
new Alert({ level, message, timeout, actionName, actionFunc })
);
}

/**
Expand Down Expand Up @@ -94,8 +117,16 @@ export class AlertController {
* @param {'success'|'info'|'warning'} type - The alert level
* @param {string} message - The message to relay to the user
* @param {number?} [timeout] - The time in `ms` until the alert expires
* @param {string?} actionName - The button title of an optional Action to perform
* @param {function?} actionFunc - The function to execute if the Action button is used
*/
export function createAlert(type, message, timeout) {
export function createAlert(type, message, timeout, actionName, actionFunc) {
const alertController = AlertController.getInstance();
return alertController.createAlert(type, message, timeout);
return alertController.createAlert(
type,
message,
timeout,
actionName,
actionFunc
);
}
12 changes: 10 additions & 2 deletions scripts/composables/use_alerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ export const useAlerts = defineStore('alerts', () => {
* @param {'success'|'info'|'warning'} type - The alert level
* @param {string} message - The message to relay to the user
* @param {number?} timeout - The time in `ms` until the alert expires (Defaults to never expiring)
* @param {string?} actionName - The button title of an optional Action to perform
* @param {function?} actionFunc - The function to execute if the Action button is used
*/
const createAlert = (type, message, timeout) => {
alertController.createAlert(type, message, timeout);
const createAlert = (type, message, timeout, actionName, actionFunc) => {
alertController.createAlert(
type,
message,
timeout,
actionName,
actionFunc
);
};

alertController.subscribe(() => {
Expand Down
14 changes: 12 additions & 2 deletions scripts/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,20 @@ function subscribeToNetworkEvents() {

getEventEmitter().on('transaction-sent', (success, result) => {
if (success) {
// Prepare an Alert action function to open the TX in the explorer
const network = useNetwork();
const openTxExplorer = () =>
window.open(network.explorerUrl + '/tx/' + result, '_blank');

// Notify the user of their transaction
createAlert(
'success',
`${ALERTS.TX_SENT}<br>${sanitizeHTML(result)}`,
result ? 1250 + result.length * 50 : 3000
`<b>${ALERTS.TX_SENT}</b><br>${sanitizeHTML(
result.substring(0, 24)
)}...`,
15000,
'Open In Explorer',
openTxExplorer
);
} else {
debugError(DebugTopics.NET, 'Error sending transaction:');
Expand Down
17 changes: 14 additions & 3 deletions tests/unit/alert.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,20 @@ describe('createAlert function', () => {

const message = 'Singleton Alert';
const level = 'info';
createAlert(level, message);

expect(createAlertSpy).toHaveBeenCalledWith(level, message, undefined);
const timeout = 10000;
const actionName = 'Example Calculation';
const actionExampleFunc = () => {
return 5 + 5;
};
createAlert(level, message, timeout, actionName, actionExampleFunc);

expect(createAlertSpy).toHaveBeenCalledWith(
level,
message,
timeout,
actionName,
actionExampleFunc
);

createAlertSpy.mockRestore();
});
Expand Down

0 comments on commit 720d2a9

Please sign in to comment.