Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
44 changes: 44 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Deploy
on:
push:
branches: main
pull_request:
branches: main
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest

permissions:
id-token: write # Needed for auth with Deno Deploy
contents: read # Needed to clone the repository

steps:
- name: Clone repository
uses: actions/checkout@v4

- name: Install Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*

- name: Install step
run: "bun install"

- name: Build step
run: "bun run build"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

- name: Upload to Deno Deploy
uses: denoland/deployctl@v1
with:
project: "stake-ubq-fi"
entrypoint: "https://deno.land/std@0.217.0/http/file_server.ts"
root: "dist"
Comment thread
3scava1i3r marked this conversation as resolved.
Outdated


12 changes: 6 additions & 6 deletions src/components/dashboard-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ICONS } from "./iconography.tsx";
import { PoolDisplay } from "./pool-display.tsx";
import { BaseError } from "viem";
import { useStatusMessage } from "../context/status-message.tsx";
import { useToast } from "../ui/toast";

const LogoSpan = () => <span id="header-logo-wrapper">{ICONS.DAO_LOGO}</span>;

Expand All @@ -13,8 +14,9 @@ export function DashboardPage() {
const { disconnect } = useDisconnect();

const { successMessage, errorMessage, setErrorMessage, clearMessages } = useStatusMessage();
const { toast } = useToast();
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const isWalletInstalled = typeof window !== "undefined" && !!window.ethereum;
const isWalletInstalled = typeof window !== "undefined" && !!(window as { ethereum?: unknown }).ethereum;

return (
<>
Expand Down Expand Up @@ -47,11 +49,9 @@ export function DashboardPage() {
{ connector: injected() },
{
onError: (error) => {
if (error instanceof BaseError) {
setErrorMessage(error.shortMessage);
} else {
setErrorMessage(error.message);
}
const message = error instanceof BaseError ? error.shortMessage : error.message;
toast(message, "error", 0);
setErrorMessage(message);
},
onSuccess: () => clearMessages(),
}
Expand Down
17 changes: 13 additions & 4 deletions src/components/pool-display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useErc20Token } from "../hooks/erc20Token.ts";
import { useState } from "react";
import { waitForTransactionReceipt } from "viem/actions";
import { useStatusMessage } from "../context/status-message.tsx";
import { useToast } from "../ui/toast";
Comment on lines +2 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused imports.

ESLint confirms useWriteContract, parseUnits, erc20Abi, and useStatusMessageState are all unused. These appear to be leftovers from removed code.

Proposed fix
-import { useReadContract, useWriteContract, usePublicClient } from "wagmi";
-import { parseUnits, BaseError } from "viem";
+import { useReadContract, usePublicClient } from "wagmi";
+import { BaseError } from "viem";
 import { stakingContract } from "../constants/contracts";
-import erc20Abi from "../abis/erc20";
 import { useErc20Token } from "../hooks/erc20Token";
 import { useState } from "react";
 import { waitForTransactionReceipt } from "viem/actions";
-import { useStatusMessageState, useStatusMessageDispatch } from "../context/status-message.tsx";
+import { useStatusMessageDispatch } from "../context/status-message.tsx";
🧰 Tools
🪛 ESLint

[error] 2-2: 'useWriteContract' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 3-3: 'parseUnits' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 5-5: 'erc20Abi' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 9-9: 'useStatusMessageState' is defined but never used.

(@typescript-eslint/no-unused-vars)

import { Button } from "./button.tsx";

interface PoolDisplayProps {
Expand All @@ -18,6 +19,7 @@ export function PoolDisplay({ poolId = 0n }: PoolDisplayProps) {
const publicClient = usePublicClient();

const { setErrorMessage, setSuccessMessage, clearMessages } = useStatusMessage();
const { toast } = useToast();

const [stakeAmount, setStakeAmount] = useState("");
const [unstakeAmount, setUnstakeAmount] = useState("");
Expand Down Expand Up @@ -82,24 +84,31 @@ export function PoolDisplay({ poolId = 0n }: PoolDisplayProps) {
try {
const receipt = await waitForTransactionReceipt(publicClient, { hash });
if (receipt.status === "success") {
toast("Transaction confirmed", "success");
setSuccessMessage("Transaction confirmed");
} else {
toast("Transaction reverted", "error", 0);
setErrorMessage("Transaction reverted");
}
} catch (error) {
setErrorMessage(error instanceof Error ? error.message : "Transaction failed");
const message = error instanceof Error ? error.message : "Transaction failed";
toast(message, "error", 0);
setErrorMessage(message);
}
refreshData();
};

const onError = (error: unknown) => {
let message: string;
if (error instanceof BaseError) {
setErrorMessage(error.shortMessage);
message = error.shortMessage;
} else if (error instanceof Error) {
setErrorMessage(error.message);
message = error.message;
} else {
setErrorMessage("An unknown error occurred");
message = "An unknown error occurred";
}
toast(message, "error", 0);
setErrorMessage(message);
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const claim = () => {
Expand Down
110 changes: 110 additions & 0 deletions src/css/toast.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Toast System Styles
* Minimal toast styles aligned with existing CSS design system
*/

#toast-portal {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 8px;
max-width: 400px;
pointer-events: none;
}

#toast-portal > * {
pointer-events: auto;
}

.toast {
padding: 12px 16px;
border-radius: 4px;
font-size: 14px;
font-family: var(--font-family, -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", sans-serif);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
animation: toast-enter 0.3s ease-out;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
transition: opacity 0.2s ease-out;
color: #ffffff;
}

.toast:hover {
opacity: 0.9;
}

.toast:focus {
outline: 2px solid var(--primary, #0066cc);
outline-offset: 2px;
}

.toast:focus:not(:focus-visible) {
outline: none;
}

.toast.info {
background-color: var(--primary, #0066cc);
border-left: 3px solid var(--primary, #0066cc);
}

.toast.success {
background-color: var(--success, #00aa55);
border-left: 3px solid var(--success, #00aa55);
}

.toast.error {
background-color: var(--error, #ff4444);
border-left: 3px solid var(--error, #ff4444);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

.toast-icon {
font-size: 16px;
flex-shrink: 0;
}

.toast-message {
flex: 1;
word-break: break-word;
line-height: 1.4;
}

@keyframes toast-enter {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}

@keyframes toast-exit {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}

.toast.exiting {
animation: toast-exit 0.2s ease-in forwards;
}

/* Responsive adjustments */
@media (max-width: 480px) {
#toast-portal {
bottom: 16px;
right: 16px;
left: 16px;
max-width: none;
}
}
11 changes: 8 additions & 3 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import App from "./App.tsx";
import { grid } from "./the-grid";
import { isLocalNode, RPC_URL } from "./constants/config";
import { StatusMessageProvider } from "./context/status-message.tsx";
import { ToastProvider, ToastContainer } from "./ui/toast";
import "./css/toast.css";

// Configure wagmi
const supportedChains: [Chain, ...Chain[]] = isLocalNode ? [mainnet, anvil] : [mainnet];
Expand Down Expand Up @@ -44,9 +46,12 @@ createRoot(rootElement).render(
<StrictMode>
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<StatusMessageProvider>
<App />
</StatusMessageProvider>
<ToastProvider>
<StatusMessageProvider>
<App />
<ToastContainer />
</StatusMessageProvider>
</ToastProvider>
</QueryClientProvider>
</WagmiProvider>
</StrictMode>
Expand Down
62 changes: 62 additions & 0 deletions src/ui/toast/ToastContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Toast Container
* Portal-based container for rendering toasts at document body level
*/

import { createPortal } from "react-dom";
import { useToast } from "./ToastProvider";
import type { ToastVariant } from "./types";

const ICONS: Record<ToastVariant, string> = {
info: "ℹ",
success: "✓",
error: "✕",
};

interface ToastItemProps {
toast: {
id: string;
message: string;
variant: ToastVariant;
};
onDismiss: (id: string) => void;
}

function ToastItem({ toast, onDismiss }: ToastItemProps) {
return (
<div
className={`toast toast-${toast.variant}`}
onClick={() => onDismiss(toast.id)}
role="alert"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
onDismiss(toast.id);
}
}}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
>
<span className="toast-icon" aria-hidden="true">
{ICONS[toast.variant]}
</span>
<span className="toast-message">{toast.message}</span>
</div>
);
}

export function ToastContainer() {
const { toasts, dismiss } = useToast();

// Don't render anything during SSR
if (typeof document === "undefined") {
return null;
}

return createPortal(
<div id="toast-portal" aria-live="polite" aria-label="Notifications">
{toasts.map((toast) => (
<ToastItem key={toast.id} toast={toast} onDismiss={dismiss} />
))}
</div>,
document.body
);
}
Loading