Skip to content
1 change: 1 addition & 0 deletions contract/contracts/hello-world/src/autoshare_logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::base::errors::Error;
use crate::base::events::{
AdminTransferred, AuthorizationFailure, AutoshareCreated, AutoshareUpdated, ContractPaused,
ContractUnpaused, GroupActivated, GroupDeactivated, NotificationCategory, NotificationExpired,
NotificationPriority, NotificationRevoked, NotificationScheduled,
NotificationExtended, NotificationPriority, NotificationRevoked, NotificationScheduled,
ScheduledNotificationCancelled, Withdrawal,
};
Expand Down
18 changes: 11 additions & 7 deletions contract/contracts/hello-world/src/tests/revocation_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ fn test_revoke_notification_emits_event() {
set_now(&test_env.env, 2_000);
client.revoke_notification(&id, &creator);

let topics = topics_of(&test_env.env, "notification_revoked").expect("revocation event must be emitted");
let topics =
topics_of(&test_env.env, "notification_revoked").expect("revocation event must be emitted");
// [0] name, [1] notification_id, [2] revoked_by, [3] category, [4] priority.
assert_eq!(topics.len(), 5);

Expand Down Expand Up @@ -327,12 +328,14 @@ fn test_revoke_event_has_high_priority() {
set_now(&test_env.env, 2_000);
client.revoke_notification(&id, &creator);

let topics = topics_of(&test_env.env, "notification_revoked").expect("revocation event must be emitted");
let topics =
topics_of(&test_env.env, "notification_revoked").expect("revocation event must be emitted");
// Last topic is priority
let priority_topic = topics.last().unwrap();
let priority = crate::base::events::NotificationPriority::try_from_val(&test_env.env, &priority_topic)
.expect("priority should be extractable");

let priority =
crate::base::events::NotificationPriority::try_from_val(&test_env.env, &priority_topic)
.expect("priority should be extractable");

assert_eq!(priority, crate::base::events::NotificationPriority::High);
}

Expand All @@ -349,12 +352,13 @@ fn test_revoke_event_has_notification_category() {
set_now(&test_env.env, 2_000);
client.revoke_notification(&id, &creator);

let topics = topics_of(&test_env.env, "notification_revoked").expect("revocation event must be emitted");
let topics =
topics_of(&test_env.env, "notification_revoked").expect("revocation event must be emitted");
// Second to last topic is category
let n = topics.len();
let category_topic = topics.get(n - 2).unwrap();
let category = NotificationCategory::try_from_val(&test_env.env, &category_topic)
.expect("category should be extractable");

assert_eq!(category, NotificationCategory::Notification);
}
67 changes: 67 additions & 0 deletions dashboard/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,61 @@
import { useEffect, useMemo, useState } from 'react';
import { EventExplorerPage } from './pages/EventExplorerPage';
import { NotificationPreferencesPage } from './pages/NotificationPreferencesPage';

const VALID_TAB_KEYS = ['explorer', 'preferences'] as const;

type TabKey = (typeof VALID_TAB_KEYS)[number];

function parseActiveTab(search: string): TabKey {
const params = new URLSearchParams(search);
const tab = params.get('tab');
return VALID_TAB_KEYS.includes(tab as TabKey) ? (tab as TabKey) : 'explorer';
}

export function App() {
const initialSearch = typeof window !== 'undefined' ? window.location.search : '';
const [activeTab, setActiveTab] = useState<TabKey>(() => parseActiveTab(initialSearch));

useEffect(() => {
if (typeof window === 'undefined') {
return;
}

const handlePopState = () => {
setActiveTab(parseActiveTab(window.location.search));
};

window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, []);

useEffect(() => {
if (typeof window === 'undefined') {
return;
}

const params = new URLSearchParams(window.location.search);
params.set('tab', activeTab);
window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`);
}, [activeTab]);

const tabLabel = useMemo(
() => (activeTab === 'preferences' ? 'Notification Preferences' : 'Event Explorer'),
[activeTab]
);

return (
<div className="app">
<header className="app__topbar">
<div className="app__brand">
<p className="app__brand-eyebrow">Notify Chain</p>
<h1>{tabLabel}</h1>
</div>

<nav className="app__nav" aria-label="Dashboard tabs">
<button
type="button"
className={`app__tab ${activeTab === 'explorer' ? 'app__tab--active' : ''}`}
import { useState } from 'react';
import { EventExplorerPage } from './pages/EventExplorerPage';
import { ExportHistoryPage } from './pages/ExportHistoryPage';
Expand All @@ -19,6 +77,15 @@ export function App() {
</button>
<button
type="button"
className={`app__tab ${activeTab === 'preferences' ? 'app__tab--active' : ''}`}
onClick={() => setActiveTab('preferences')}
>
Notification Preferences
</button>
</nav>
</header>

{activeTab === 'preferences' ? <NotificationPreferencesPage /> : <EventExplorerPage />}
className={`nav-tab-btn ${activeTab === 'exports' ? 'nav-tab-btn--active' : ''}`}
onClick={() => setActiveTab('exports')}
>
Expand Down
Loading
Loading