-
Notifications
You must be signed in to change notification settings - Fork 16
Toasts #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Toasts #15
Changes from 3 commits
f73abaa
b3908aa
92bb29e
8439624
16cbeb6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| name: Deploy | ||
| on: | ||
| push: | ||
| branches: main | ||
| pull_request: | ||
| branches: main | ||
|
|
||
| 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" | ||
|
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" | ||
|
3scava1i3r marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unused imports. ESLint confirms 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. ( [error] 3-3: 'parseUnits' is defined but never used. ( [error] 5-5: 'erc20Abi' is defined but never used. ( [error] 9-9: 'useStatusMessageState' is defined but never used. ( |
||
| import { Button } from "./button.tsx"; | ||
|
|
||
| interface PoolDisplayProps { | ||
|
|
@@ -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(""); | ||
|
|
@@ -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); | ||
| }; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| const claim = () => { | ||
|
|
||
| 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); | ||
| } | ||
|
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; | ||
| } | ||
| } | ||
| 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); | ||
| } | ||
| }} | ||
|
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 | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.