diff --git a/src/App.tsx b/src/App.tsx
index e5f5916..abee26b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -6,6 +6,9 @@ import Send from '@/pages/Send';
import Receive from '@/pages/Receive';
import Privacy from '@/pages/Privacy';
import { HelpButton } from '@/components/HelpButton';
+import { UndoToast } from '@/components/UndoToast';
+import Contacts from '@/pages/Contacts';
+import Notifications from '@/pages/Notifications';
import Vault from '@/pages/Vault';
import Schedule from '@/pages/Schedule';
@@ -23,10 +26,13 @@ export function App() {
} />
} />
} />
+ } />
+ } />
} />
+
);
}
diff --git a/src/components/UndoToast.tsx b/src/components/UndoToast.tsx
new file mode 100644
index 0000000..b0dd4ff
--- /dev/null
+++ b/src/components/UndoToast.tsx
@@ -0,0 +1,65 @@
+import { useEffect, useState } from 'react';
+import { useUndoStore } from '@/stores/undoStore';
+
+function ToastItem({
+ id,
+ message,
+ onUndo,
+ expiresAt,
+}: {
+ id: string;
+ message: string;
+ onUndo: () => void;
+ expiresAt: number;
+}) {
+ const removeToast = useUndoStore((s) => s.removeToast);
+ const [remaining, setRemaining] = useState(5);
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ const secs = Math.ceil((expiresAt - Date.now()) / 1000);
+ setRemaining(secs > 0 ? secs : 0);
+ }, 200);
+ return () => clearInterval(interval);
+ }, [expiresAt]);
+
+ const handleUndo = () => {
+ onUndo();
+ removeToast(id);
+ };
+
+ return (
+
+ {message}
+
+
+ {remaining}s
+
+
+
+ );
+}
+
+export function UndoToast() {
+ const toasts = useUndoStore((s) => s.toasts);
+
+ if (toasts.length === 0) return null;
+
+ return (
+
+ {toasts.map((t) => (
+
+ ))}
+
+ );
+}
diff --git a/src/pages/Contacts.tsx b/src/pages/Contacts.tsx
new file mode 100644
index 0000000..c1725c5
--- /dev/null
+++ b/src/pages/Contacts.tsx
@@ -0,0 +1,64 @@
+import { useState } from 'react';
+import { useUndoStore } from '@/stores/undoStore';
+
+interface Contact {
+ id: string;
+ name: string;
+ address: string;
+}
+
+const INITIAL_CONTACTS: Contact[] = [
+ { id: '1', name: 'Alice', address: 'GBVR...XYZW' },
+ { id: '2', name: 'Bob', address: 'GCBA...MNOP' },
+ { id: '3', name: 'Carol', address: 'GDZT...QRST' },
+];
+
+export default function Contacts() {
+ const [contacts, setContacts] = useState(INITIAL_CONTACTS);
+ const addToast = useUndoStore((s) => s.addToast);
+
+ const deleteContact = (contact: Contact) => {
+ const previous = [...contacts];
+ setContacts((c) => c.filter((x) => x.id !== contact.id));
+ addToast(`Deleted "${contact.name}"`, () => {
+ setContacts(previous);
+ });
+ };
+
+ return (
+
+
+
+ Contacts
+
+
+ Manage your saved contacts.
+
+
+
+
+ );
+}
diff --git a/src/pages/Notifications.tsx b/src/pages/Notifications.tsx
new file mode 100644
index 0000000..8928bb8
--- /dev/null
+++ b/src/pages/Notifications.tsx
@@ -0,0 +1,105 @@
+import { useState } from 'react';
+import { useUndoStore } from '@/stores/undoStore';
+
+interface Notification {
+ id: string;
+ title: string;
+ body: string;
+ read: boolean;
+}
+
+const INITIAL_NOTIFICATIONS: Notification[] = [
+ { id: '1', title: 'Payment received', body: 'You received 10 XLM from Alice.', read: false },
+ { id: '2', title: 'Contact added', body: 'Bob was added to your contacts.', read: false },
+ {
+ id: '3',
+ title: 'Schedule executed',
+ body: 'Recurring payment to Carol completed.',
+ read: true,
+ },
+];
+
+export default function Notifications() {
+ const [notifications, setNotifications] = useState(INITIAL_NOTIFICATIONS);
+ const [filter, setFilter] = useState<'all' | 'unread'>('all');
+ const addToast = useUndoStore((s) => s.addToast);
+
+ const dismiss = (notification: Notification) => {
+ const previous = [...notifications];
+ setNotifications((n) => n.filter((x) => x.id !== notification.id));
+ addToast(`Dismissed "${notification.title}"`, () => {
+ setNotifications(previous);
+ });
+ };
+
+ const clearFilter = () => {
+ const previousFilter = filter;
+ setFilter('all');
+ addToast('Filter cleared', () => {
+ setFilter(previousFilter);
+ });
+ };
+
+ const filtered = filter === 'unread' ? notifications.filter((n) => !n.read) : notifications;
+
+ return (
+
+
+
+ Notifications
+
+
+
+ {(['all', 'unread'] as const).map((f) => (
+
+ ))}
+
+ {filter !== 'all' && (
+
+ )}
+
+
+
+
+ );
+}
diff --git a/src/stores/undoStore.ts b/src/stores/undoStore.ts
new file mode 100644
index 0000000..d578fed
--- /dev/null
+++ b/src/stores/undoStore.ts
@@ -0,0 +1,32 @@
+import { create } from 'zustand';
+
+export interface UndoEntry {
+ id: string;
+ message: string;
+ onUndo: () => void;
+ expiresAt: number;
+}
+
+interface UndoState {
+ toasts: UndoEntry[];
+ addToast: (message: string, onUndo: () => void) => string;
+ removeToast: (id: string) => void;
+}
+
+export const useUndoStore = create((set) => ({
+ toasts: [],
+ addToast: (message, onUndo) => {
+ const id = Math.random().toString(36).substring(2, 9);
+ const expiresAt = Date.now() + 5000;
+ set((state) => ({
+ toasts: [...state.toasts, { id, message, onUndo, expiresAt }],
+ }));
+ setTimeout(() => {
+ set((state) => ({ toasts: state.toasts.filter((t) => t.id !== id) }));
+ }, 5000);
+ return id;
+ },
+ removeToast: (id) => {
+ set((state) => ({ toasts: state.toasts.filter((t) => t.id !== id) }));
+ },
+}));