Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -982,8 +982,10 @@
</div>

<div v-else-if="user && !publicModeVisible" class="dashboard-shell">
<div v-if="toastMessage" class="toast dashboard-toast" role="status" aria-live="polite">
{{ toastMessage }}
<div v-if="toastMessage" class="toast dashboard-toast" :class="toastType" role="alert" aria-live="polite" @click="dismissToast">
<span class="toast-icon">{{ toastIcon }}</span>
<span class="toast-text">{{ toastMessage }}</span>
<button class="toast-dismiss" type="button" aria-label="Dismiss" @click.stop="dismissToast">&times;</button>
</div>

<aside class="dash-sidebar" aria-label="Customer navigation">
Expand Down Expand Up @@ -1046,7 +1048,8 @@
<div class="dash-top-actions">
<button class="dash-icon-button" aria-label="Notifications" type="button" @click="openDashboardSection('notifications')">
<Bell :size="18" />
<span>{{ dashboardNotificationCount }}</span>
<span v-if="dashboardUnreadCount > 0" class="notif-badge">{{ dashboardUnreadCount > 99 ? '99+' : dashboardUnreadCount }}</span>
<span v-else class="notif-badge-zero">{{ dashboardNotificationCount }}</span>
</button>
<button class="primary-button compact" type="button" @click="openProjectWizard">
<Plus :size="16" />
Expand Down Expand Up @@ -1355,8 +1358,10 @@
</div>

<div v-else class="home-shell">
<div v-if="toastMessage" class="toast" role="status" aria-live="polite">
{{ toastMessage }}
<div v-if="toastMessage" class="toast" :class="toastType" role="alert" aria-live="polite" @click="dismissToast">
<span class="toast-icon">{{ toastIcon }}</span>
<span class="toast-text">{{ toastMessage }}</span>
<button class="toast-dismiss" type="button" aria-label="Dismiss" @click.stop="dismissToast">&times;</button>
</div>

<header class="home-navbar">
Expand Down Expand Up @@ -2383,6 +2388,11 @@ const errorMessage = ref('');
const showPassword = ref(false);
const showConfirmPassword = ref(false);
const toastMessage = ref('');
const toastType = ref('info');
const toastIcon = computed(() => {
const icons = { success: '✓', error: '✕', warning: '⚠', info: 'ℹ' };
return icons[toastType.value] || 'ℹ';
});
const publicNotifications = ref([]);
let toastTimer = 0;

Expand Down Expand Up @@ -3282,6 +3292,7 @@ const dashboardNotificationRows = computed(() =>
.map(mapDashboardNotification),
);
const dashboardNotificationCount = computed(() => Math.min(9, dashboardNotificationRows.value.length));
const dashboardUnreadCount = computed(() => dashboardNotificationRows.filter(n => !n.read).length);

const marketplaceBenefits = [
{
Expand Down Expand Up @@ -3425,21 +3436,26 @@ async function handleGitHubCallback() {
showToast(auth.user?.wallet_address ? 'GitHub linked to your MRG wallet.' : 'Logged in with GitHub.');
} catch (error) {
errorMessage.value = error.message;
showToast(error.message);
showToast(error.message, 'error');
} finally {
authBusy.value = false;
}
return true;
}

function showToast(message) {
function showToast(message, type) {
toastMessage.value = message;
pushPublicNotification(message);
toastType.value = type || 'info';
if (!hasWindow) return;
if (toastTimer) window.clearTimeout(toastTimer);
toastTimer = window.setTimeout(() => {
toastMessage.value = '';
}, 2200);
}, 3500);
}

function dismissToast() {
toastMessage.value = '';
if (toastTimer) window.clearTimeout(toastTimer);
}

function pushPublicNotification(message) {
Expand Down
64 changes: 64 additions & 0 deletions frontend/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -8221,3 +8221,67 @@ svg {
grid-template-columns: 1fr;
}
}


/* --- Toast Notification Styles (Issue #19) --- */
.toast.success { background: #065f46 !important; }
.toast.error { background: #991b1b !important; }
.toast.warning { background: #92400e !important; }
.toast.info { background: #1e3a5f !important; }

.toast-icon {
flex: 0 0 20px;
font-size: 14px;
font-weight: 920;
opacity: 0.9;
}

.toast-text {
flex: 1;
text-align: left;
}

.toast-dismiss {
flex: 0 0 24px;
background: none;
border: none;
color: inherit;
font-size: 18px;
cursor: pointer;
opacity: 0.6;
padding: 0;
line-height: 1;
}

.toast-dismiss:hover { opacity: 1; }

.notif-badge {
position: absolute;
top: -2px;
right: -4px;
min-width: 16px;
height: 16px;
padding: 0 4px;
border-radius: 8px;
background: #dc2626;
color: #fff;
font-size: 10px;
font-weight: 920;
line-height: 16px;
text-align: center;
}

.notif-badge-zero {
position: absolute;
top: -2px;
right: -2px;
width: 14px;
height: 14px;
border-radius: 7px;
background: #e5e7eb;
color: #6b7280;
font-size: 9px;
font-weight: 750;
line-height: 14px;
text-align: center;
}