diff --git a/docs/components/WalletConnectButton.md b/docs/components/WalletConnectButton.md new file mode 100644 index 0000000..8211d1b --- /dev/null +++ b/docs/components/WalletConnectButton.md @@ -0,0 +1,38 @@ +# WalletConnectButton + +The `WalletConnectButton` component provides a unified control for users to connect and manage their crypto wallet session within the TalentTrust application. + +## Location +`src/components/WalletConnectButton.tsx` + +## Usage + +```tsx +import { WalletConnectButton } from '@/components/WalletConnectButton'; + +export function Header() { + return ( +
+ +
+ ); +} +``` + +## Features +- **Global State Integration:** Uses `useWallet` context to ensure the connection state is shared across the app, such as gating actions in `ActionPanel`. +- **States:** + - **Disconnected:** Displays a prominent "Connect Wallet" button. + - **Connecting:** Displays a loading spinner and "Connecting..." text. Disables the button. + - **Error:** Displays a "Connection Error" message with a "Retry" link. + - **Connected:** Displays the truncated wallet address along with options to copy to clipboard or disconnect. +- **Accessibility:** Fully accessible with ARIA labels, semantic HTML, and proper focus states. Buttons are keyboard operable. +- **Responsiveness:** Works across mobile and desktop viewpoints. + +## Dependencies +- `lucide-react` (icons) or inline SVGs. +- `@/contexts/WalletContext` +- `@/lib/truncateAddress` + +## Testing +Tested with Jest and React Testing Library in `src/components/__tests__/WalletConnectButton.test.tsx`. Covers all UI states and interactions (click, copy, etc.). diff --git a/jest.setup.js b/jest.setup.js index 6df2017..04c0fe2 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -28,3 +28,14 @@ const localStorageMock = (() => { Object.defineProperty(window, 'localStorage', { value: localStorageMock }); +// Global mock for WalletContext +jest.mock('@/contexts/WalletContext', () => ({ + useWallet: jest.fn().mockReturnValue({ + address: '0x123', + isConnecting: false, + error: null, + connect: jest.fn(), + disconnect: jest.fn(), + }), + WalletProvider: ({ children }) => children, +})); diff --git a/package-lock.json b/package-lock.json index 40c421b..2f2a71c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,6 +81,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1613,6 +1614,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -2065,6 +2067,7 @@ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -2076,6 +2079,7 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -2156,6 +2160,7 @@ "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", @@ -2683,6 +2688,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3314,6 +3320,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -4329,6 +4336,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4498,6 +4506,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7200,6 +7209,7 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -8314,6 +8324,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -8623,6 +8634,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -8635,6 +8647,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -9780,6 +9793,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10018,6 +10032,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1f1e6d2..083773a 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -9,6 +9,8 @@ export const metadata: Metadata = { import { PreferencesProvider } from '@/lib/preferences'; import { SettingsTrigger } from '@/components/settings/SettingsTrigger'; +import { WalletProvider } from '@/contexts/WalletContext'; +import { WalletConnectButton } from '@/components/WalletConnectButton'; export default function RootLayout({ children, @@ -20,8 +22,20 @@ export default function RootLayout({ - {children} - + +
+
+
+ TalentTrust +
+ +
+
+ {children} +
+
+ +
diff --git a/src/components/ActionPanel.tsx b/src/components/ActionPanel.tsx index 8cba46c..8c80a56 100644 --- a/src/components/ActionPanel.tsx +++ b/src/components/ActionPanel.tsx @@ -1,5 +1,7 @@ 'use client'; +import { useWallet } from '@/contexts/WalletContext'; + export type ActionPanelProps = { status: 'Active' | 'Completed' | 'Disputed' | 'Pending'; onSubmitMilestone?: () => void; @@ -29,12 +31,20 @@ const ActionPanel = ({ onViewSummary, }: ActionPanelProps) => { const actions = getActionButtons(status); + const { address } = useWallet(); + const isWalletConnected = !!address; + const noWalletMsg = 'Connect wallet to perform this action'; return (