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}
-
+
+
+
+
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 (