Skip to content
Merged
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
43 changes: 43 additions & 0 deletions UI-DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@

---

## 🧩 Figma Source

Figma file: https://www.figma.com/file/qkTJWD2iKj4W2BVztdCrHt/SoroScan-UI-Design?node-id=0-1

This repository follows the Figma master file as the single source of truth for colors, typography, spacing, and component behavior.

---

## 📱 Responsive System

- Mobile: single-column stacked cards, large touch targets, high-contrast controls
- Tablet: grouped panels with compact button rows
- Desktop: wider layouts with side-by-side sections and status surfaces
- Breakpoints: mobile first, then `sm` and `md` scale-up layouts across the UI

---

## 📐 Design System Foundation

### Color Palette
Expand Down Expand Up @@ -226,6 +243,32 @@ SPECS
└──────────────────────────────────────────────────────────┘
```

## 📊 Comparison & Diff View

```
┌───────────────────────────────────────────────────────────────────────────┐
│ [EVENT A] │ [EVENT B] │
├───────────────────────────────────────────────────────────────────────────┤
│ Timestamp: 2026-05-29 12:24 │ Timestamp: 2026-05-29 12:44 │
│ Event Type: Transfer │ Event Type: Transfer │
├───────────────────────────────────────────────────────────────────────────┤
│ Field │ A Value │ B Value │ Status │
├───────────────────────────────────────────────────────────────────────────┤
│ from │ 0xabc...123 │ 0xabc...123 │ unchanged │
│ to │ 0xdef...456 │ 0xdef...789 │ modified │
│ amount │ 100 │ 250 │ modified │
│ memo │ "swap" │ — │ deleted │
│ reward │ — │ 10 │ added │
└───────────────────────────────────────────────────────────────────────────┘
```

- Side-by-side layout shows event details in parallel columns.
- Additions: green background, deletions: red, modified: amber, unchanged: neutral.
- Alternative unified diff option uses inline change markers (+/−) with syntax-highlighted JSON/code blocks.
- Mobile stacks event sections vertically with clear headers and toggle buttons to switch between A/B views.
- Includes summary metrics: lines added, removed, changed.
- Supports expandable rows for large payloads and collapsed sections for unchanged fields.

### 5. Modal / Dialog

```
Expand Down
43 changes: 43 additions & 0 deletions soroscan-frontend/__tests__/confirmation-dialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { render, screen, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom";
import { ConfirmationDialog } from "../components/ui/confirmation-dialog";

describe("ConfirmationDialog", () => {
it("renders title, description, and buttons", () => {
render(
<ConfirmationDialog
open={true}
title="Delete API key"
description="This action cannot be undone."
confirmText="Revoke"
cancelText="Keep"
onConfirm={jest.fn()}
onCancel={jest.fn()}
/>
);

expect(screen.getByText("Delete API key")).toBeInTheDocument();
expect(screen.getByText("This action cannot be undone.")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /revoke/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /keep/i })).toBeInTheDocument();
});

it("calls callbacks when buttons are clicked", () => {
const onConfirm = jest.fn();
const onCancel = jest.fn();

render(
<ConfirmationDialog
open={true}
onConfirm={onConfirm}
onCancel={onCancel}
/>
);

fireEvent.click(screen.getByRole("button", { name: /cancel/i }));
expect(onCancel).toHaveBeenCalledTimes(1);

fireEvent.click(screen.getByRole("button", { name: /confirm/i }));
expect(onConfirm).toHaveBeenCalledTimes(1);
});
});
14 changes: 12 additions & 2 deletions soroscan-frontend/app/settings/components/APIKeyManager.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";
import { useState } from "react";
import { ConfirmationDialog } from "@/components/ui/confirmation-dialog";

Check warning on line 3 in soroscan-frontend/app/settings/components/APIKeyManager.tsx

View workflow job for this annotation

GitHub Actions / lint-and-test

'ConfirmationDialog' is defined but never used

type APIKey = {
id: string;
Expand Down Expand Up @@ -30,6 +31,8 @@
return [];
});
const [copied, setCopied] = useState<string | null>(null);
const [confirmingKey, setConfirmingKey] = useState<string | null>(null);
const [isRevoking, setIsRevoking] = useState(false);

Check warning on line 35 in soroscan-frontend/app/settings/components/APIKeyManager.tsx

View workflow job for this annotation

GitHub Actions / lint-and-test

'isRevoking' is assigned a value but never used

const saveKeys = (newKeys: APIKey[]) => {
setKeys(newKeys);
Expand All @@ -49,8 +52,15 @@
saveKeys([...keys, newKey]);
};

const handleRevoke = (id: string) => {
saveKeys(keys.filter((k) => k.id !== id));
const requestRevoke = (id: string) => setConfirmingKey(id);

Check warning on line 55 in soroscan-frontend/app/settings/components/APIKeyManager.tsx

View workflow job for this annotation

GitHub Actions / lint-and-test

'requestRevoke' is assigned a value but never used

const handleConfirmRevoke = () => {

Check warning on line 57 in soroscan-frontend/app/settings/components/APIKeyManager.tsx

View workflow job for this annotation

GitHub Actions / lint-and-test

'handleConfirmRevoke' is assigned a value but never used
if (!confirmingKey) return;
setIsRevoking(true);
const nextKeys = keys.filter((k) => k.id !== confirmingKey);
saveKeys(nextKeys);
setConfirmingKey(null);
setIsRevoking(false);
};

const handleCopy = (key: string) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export default function NotificationPrefs() {
<div key={key} className="flex items-center justify-between gap-3 rounded-xl border border-green-500/10 bg-[#09132f]/80 px-4 py-3">
<span className="font-mono text-sm text-green-300">{label}</span>
<button
aria-pressed={prefs[key]}
onClick={() => toggle(key)}
className={`w-14 rounded-full border px-3 py-1 text-[11px] font-mono transition-colors ${
prefs[key]
Expand Down
67 changes: 67 additions & 0 deletions soroscan-frontend/components/ui/confirmation-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"use client";

import * as React from "react";
import { Button } from "@/components/ui/button";
import {
Modal,
ModalContent,
ModalHeader,
ModalTitle,
ModalDescription,
} from "@/components/ui/modal";

interface ConfirmationDialogProps {
open: boolean;
title?: string;
description?: string;
confirmText?: string;
cancelText?: string;
loading?: boolean;
onConfirm: () => void;
onCancel: () => void;
}

export function ConfirmationDialog({
open,
title = "Confirm action",
description,
confirmText = "Confirm",
cancelText = "Cancel",
loading = false,
onConfirm,
onCancel,
}: ConfirmationDialogProps) {
return (
<Modal open={open} onOpenChange={(isOpen) => !isOpen && onCancel()}>
<ModalContent className="max-w-md" aria-label={title}>
<ModalHeader>
<ModalTitle>{title}</ModalTitle>
{description ? (
<ModalDescription>{description}</ModalDescription>
) : null}
</ModalHeader>

<div className="grid gap-3 pt-4">
<div className="flex flex-col gap-3 sm:flex-row sm:justify-end">
<Button
variant="outline"
onClick={onCancel}
disabled={loading}
className="w-full sm:w-auto"
>
{cancelText}
</Button>
<Button
variant="destructive"
onClick={onConfirm}
disabled={loading}
className="w-full sm:w-auto"
>
{loading ? `${confirmText}...` : confirmText}
</Button>
</div>
</div>
</ModalContent>
</Modal>
);
}
Loading