Conversation
- Add ActaClient class for interacting with ACTA API endpoints - Include methods for pinging the service, checking health, and managing credentials - Define type interfaces for API responses, credential creation, and client configuration - Implement error handling and request timeout management Resolves: Establish a structured client for ACTA API operations
- Implement methods for creating, updating, and deleting credentials - Enhance error handling for credential operations - Update type interfaces to reflect new credential management features Resolves: Improved functionality for managing credentials within the ACTA API client
- Introduce new types for participation duration, risk levels, performance tiers, and pool type experience for credentials. - Update CreateCredentialBody interface to align with the actual API response structure. - Refactor API endpoint paths to remove the 'acta' prefix for a cleaner interface. - Add a new method to list all credentials. Resolves: Improved structure and functionality for credential management in the ACTA API client.
- Introduce an optional onSuccess prop to the CreateCredentialModal component. - Invoke onSuccess callback upon successful credential creation to enhance user feedback. Resolves: Improved user experience by providing a callback for successful operations in the credential creation process.
…omponent - Adjust the layout of the CredentialCard component by removing unnecessary flex properties for better alignment. - Enhance the styling of the status badge to improve visual clarity and user experience. Resolves: Improved UI consistency and readability in the CredentialCard component.
- Remove error parameter from catch blocks in CredentialCard component to streamline error handling. - Update toast messages to maintain user feedback without exposing error details. Resolves: Improved code clarity and consistency in error handling for credential operations.
…pplyUSDCModal - Add CredentialSelector component to BorrowModal and SupplyUSDCModal for optional credential selection. - Implement state management for selected credentials to enhance user experience and unlock better terms. - Include UI updates to display selected credential information and potential benefits. Resolves: Improved functionality and user engagement through credential verification options in borrowing and supplying processes.
WalkthroughAdds ACTA integration across the frontend: environment variables, a typed Acta client library, comprehensive ACTA TypeScript types, hooks for credential creation and management, UI components (create modal, card, selector, dashboard), dev/test pages, and optional credential selection UI embedded in marketplace modals. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as CreateCredentialModal
participant Hook as useCredentialCreate
participant Client as ActaClient
participant API as ACTA API
participant LS as localStorage
participant Toast as Toast
User->>UI: Submit credential form
UI->>Hook: createCredential(formData)
Hook->>Client: createCredential(body)
Client->>API: POST /credentials
API-->>Client: ActaResponse<CredentialData>
Client-->>Hook: ActaResponse
alt success
Hook->>LS: Save LocalCredentialRecord (index)
Hook-->>UI: CredentialCreateResult
UI->>Toast: show success
UI-->>User: close/reset modal
else error
Hook-->>UI: throw/error
UI->>Toast: show error
end
sequenceDiagram
autonumber
actor User
participant Parent as Borrow/Supply Modal
participant Selector as CredentialSelector
participant Hook as useCredentials
participant LS as localStorage
participant Client as ActaClient
participant API as ACTA API
User->>Parent: open modal
Parent->>Selector: open selector dialog
Selector->>Hook: refreshCredentials()
Hook->>LS: load stored credentials
par fetch statuses
Hook->>Client: getByContractId / listCredentials
Client->>API: GET /credentials...
API-->>Client: ActaResponse
Client-->>Hook: status/data
end
Hook-->>Selector: credentials (filtered Active)
User->>Selector: choose credential
Selector-->>Parent: onCredentialSelect(credential)
Parent-->>User: display selected credential details (risk/performance)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (26)
frontend/src/components/modules/credentials/ui/components/CredentialCard.tsx (4)
47-61: Deduplicate status color/icon maps across components.
statusColorsandstatusIconsare duplicated elsewhere (e.g., CredentialSelector). Extract to a shared constants module to keep styling consistent and reduce drift.
279-286: Disable “View Contract Details” when details are missing.Avoid opening an empty dialog if
contractId/hashare not present; add a title for clarity.Apply this diff:
- <Button + <Button variant="outline" size="sm" - className="w-full btn-secondary" + className="w-full btn-secondary" + disabled={!contractId || !hash} + title={!contractId || !hash ? "Contract details unavailable" : undefined} >
339-347: Disable “Include contract details” when IDs are unavailable.Prevent toggling when
contractId/hashare missing; keeps privacy control semantics clear.Apply this diff:
- <Switch + <Switch id="show-contract-details" checked={showContractDetails} - onCheckedChange={setShowContractDetails} + onCheckedChange={setShowContractDetails} + disabled={!contractId || !hash} /> - <Label htmlFor="show-contract-details" className="text-white"> + <Label + htmlFor="show-contract-details" + className={`text-white ${!contractId || !hash ? "opacity-50" : ""}`} + >
116-126: Use a consistent confirmation dialog instead of window.confirm.
window.confirmbreaks visual consistency and is harder to test. Reuse your Dialog component for the remove confirmation.frontend/src/components/modules/credentials/ui/components/CredentialSelector.tsx (2)
44-49: Share status color mapping with CredentialCard.This mapping duplicates
statusColors. Import from a shared constants file to avoid divergence.
122-125: Memoize active credential filtering.Prevents redundant filtering on re-renders.
Apply this diff:
- const activeCredentials = credentials.filter( - (c) => (c.status || "Active") === "Active", - ); + const activeCredentials = React.useMemo( + () => credentials.filter((c) => (c.status || "Active") === "Active"), + [credentials], + );And ensure
useMemois available viaReact.useMemo(no import change needed).frontend/src/components/modules/credentials/ui/components/CreateCredentialModal.tsx (1)
235-236: Close only when dialog requests closing.
onOpenChange={handleClose}ignores the boolean arg and can fire on open as well. Only close whenopenbecomes false.Apply this diff:
- <Dialog open={isOpen} onOpenChange={handleClose}> + <Dialog open={isOpen} onOpenChange={(open) => !open && handleClose()}>frontend/src/components/modules/marketplace/ui/components/BorrowModal.tsx (1)
195-227: Optional: Extract collateral factor to a constant.
85is used multiple times; hoist to a named constant for clarity and easier tuning.frontend/src/app/dev/credential-test/page.tsx (3)
27-31: Memoize ActaClient in the dev page too.Avoid recreating the client on each render; keeps identity stable for components using it.
Apply this diff:
- const client = new ActaClient({ - baseUrl: process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", - apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, - }); + const client = React.useMemo( + () => + new ActaClient({ + baseUrl: process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", + apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, + }), + [], + );
381-387: Auto-refresh local list after creation.Wire
onSuccessto refresh the local index and close the modal for a tighter feedback loop.Apply this diff:
<CreateCredentialModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} client={client} + onSuccess={() => { + const updated = getCredentialsFromLocalStorage(); + setCredentials(updated); + setIsModalOpen(false); + }} />
239-243: Use transactionHash or remove TX row
The TX row repeats the 8-char hash becausecredentialis aLocalCredentialRecordwithout atransactionHash. Either persist and displaytransactionHash, or drop the TX row.
Location: frontend/src/app/dev/credential-test/page.tsx:239-243frontend/src/components/modules/credentials/hooks/useCredentialCreate.ts (6)
75-77: Avoid deprecatedsubstrand prefer stable unique IDs.Use
crypto.randomUUID()with a fallback tosliceto avoid deprecated APIs and improve uniqueness.-function generateLocalId(): string { - return `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; -} +function generateLocalId(): string { + const rnd = + typeof crypto !== "undefined" && "randomUUID" in crypto + ? (crypto as Crypto).randomUUID() + : Math.random().toString(36).slice(2, 11); + return `local_${rnd}`; +}
82-95: Guard localStorage access for SSR/unavailable storage.Next.js may render server-side; also storage can be disabled. Add a guard to silently skip when not available.
function saveCredentialToLocalStorage(record: LocalCredentialRecord): void { try { + if (typeof window === "undefined" || !("localStorage" in window)) return; const existingIndex = localStorage.getItem(CREDENTIAL_INDEX_KEY); const index: LocalCredentialRecord[] = existingIndex ? JSON.parse(existingIndex) : []; index.push(record); localStorage.setItem(CREDENTIAL_INDEX_KEY, JSON.stringify(index)); } catch (error) {
119-130: Validate optional expiration occurs after issuance.Prevent creating an already-expired credential.
- issuanceDate: new Date().toISOString(), - expirationDate: formData.expirationDate, + issuanceDate: new Date().toISOString(), + expirationDate: formData.expirationDate, }; + if (credentialData.expirationDate) { + const exp = Date.parse(credentialData.expirationDate); + const iss = Date.parse(credentialData.issuanceDate); + if (Number.isFinite(exp) && Number.isFinite(iss) && exp <= iss) { + throw new Error("Expiration date must be after issuance date"); + } + }
179-186: Trim console noise in production.Remove or guard console logging that can leak PII/structure and clutter logs.
- saveCredentialToLocalStorage(localRecord); - console.log("Saved to localStorage:", localRecord); + saveCredentialToLocalStorage(localRecord);
216-224: Guard localStorage read for SSR/unavailable storage.Return an empty list when storage is not present.
export function getCredentialsFromLocalStorage(): LocalCredentialRecord[] { try { + if (typeof window === "undefined" || !("localStorage" in window)) return []; const data = localStorage.getItem(CREDENTIAL_INDEX_KEY); return data ? JSON.parse(data) : []; } catch (error) {
229-244: Guard localStorage write/remove for SSR/unavailable storage.Avoid exceptions when storage is not available.
export function removeCredentialFromLocalStorage(localId: string): boolean { try { + if (typeof window === "undefined" || !("localStorage" in window)) return false; const existingIndex = localStorage.getItem(CREDENTIAL_INDEX_KEY); const index: LocalCredentialRecord[] = existingIndex ? JSON.parse(existingIndex) : [];frontend/src/components/modules/credentials/hooks/useCredentials.ts (3)
195-242: Rollback to previous status, not hard-coded "Active".Optimistic rollback should restore the prior value for that credential. Also add
credentialsas a dependency to avoid stale closures.- const updateCredentialStatus = useCallback( + const updateCredentialStatus = useCallback( async ( contractId: string, newStatus: "Active" | "Revoked" | "Suspended", ): Promise<boolean> => { - // Optimistic update + // Capture previous status for rollback + const prevStatus = + credentials.find((c) => c.contractId === contractId)?.status ?? "Active"; + // Optimistic update setCredentials((prevCredentials) => prevCredentials.map((credential) => credential.contractId === contractId ? { ...credential, status: newStatus } : credential, ), ); try { // Update on server const success = await updateCredentialStatusOnServer( client, contractId, newStatus, ); if (!success) { // Rollback optimistic update on error setCredentials((prevCredentials) => prevCredentials.map((credential) => credential.contractId === contractId - ? { ...credential, status: "Active" } // Rollback to default + ? { ...credential, status: prevStatus } : credential, ), ); return false; } return true; } catch { // Rollback optimistic update on error setCredentials((prevCredentials) => prevCredentials.map((credential) => credential.contractId === contractId - ? { ...credential, status: "Active" } // Rollback to default + ? { ...credential, status: prevStatus } : credential, ), ); return false; } }, - [client], + [client, credentials], );
279-287: Initialize status counters to avoid undefined keys.Ensure all filter keys exist, even when zero.
- const credentialsByStatus = credentials.reduce( - (acc, credential) => { + const credentialsByStatus = credentials.reduce( + (acc, credential) => { const status = credential.status || "Active"; acc[status] = (acc[status] || 0) + 1; acc["All"] = (acc["All"] || 0) + 1; return acc; }, - {} as Record<StatusFilter, number>, + { All: 0, Active: 0, Revoked: 0, Suspended: 0 } as Record<StatusFilter, number>, );
333-341: Provide clipboard fallback for older browsers / insecure contexts.Navigator clipboard can be unavailable. Add a textarea fallback.
export async function copyToClipboard(text: string): Promise<boolean> { try { - await navigator.clipboard.writeText(text); - return true; + if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(text); + return true; + } + // Fallback + if (typeof document !== "undefined") { + const ta = document.createElement("textarea"); + ta.value = text; + ta.style.position = "fixed"; + ta.style.left = "-9999px"; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + const ok = document.execCommand("copy"); + document.body.removeChild(ta); + return ok; + } + return false; } catch (clipboardError) { console.error("Failed to copy to clipboard:", clipboardError); return false; } }frontend/src/@types/acta.types.ts (2)
112-119: Narrow status typing or introduce a shared union to avoid stringly-typed code.status is plain string across responses. Consider a shared union (for local lifecycle status) to improve safety and align UI code.
Example addition:
export interface CreateCredentialRes { id: string; hash: string; - status: string; + status: string; // Keep API as string if server uses varied values createdAt: string; updatedAt: string; credential: CreateCredentialReq; } + +// Local lifecycle status used in UI and filters +export type CredentialLifecycleStatus = "Active" | "Suspended" | "Revoked";
141-153: Constrain update status request/response with a local union.If the UI only supports "Active" | "Suspended" | "Revoked", reflect that in the update shapes to prevent invalid updates.
-export interface UpdateCredentialStatusReq { - status: string; -} +export interface UpdateCredentialStatusReq { + status: CredentialLifecycleStatus; +} export interface UpdateCredentialStatusRes { id: string; hash: string; - status: string; + status: CredentialLifecycleStatus; updatedAt: string; }frontend/src/app/dev/credentials-dashboard/page.tsx (1)
12-15: Memoize ActaClient to avoid re-instantiation on re-renders.Saves needless allocations and avoids re-creating the client on each render.
-import React from "react"; +import React, { useMemo } from "react"; ... - const client = new ActaClient({ - baseUrl: process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", - apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, - }); + const client = useMemo( + () => + new ActaClient({ + baseUrl: + process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", + apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, + }), + [], + );frontend/src/app/dev/credential-selector/page.tsx (1)
26-29: Memoize ActaClient instance.Same reasoning as the dashboard test page; reduces churn.
-import { ActaClient } from "@/lib/acta/client"; +import { ActaClient } from "@/lib/acta/client"; +import { useMemo } from "react"; ... - const client = new ActaClient({ - baseUrl: process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", - apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, - }); + const client = useMemo( + () => + new ActaClient({ + baseUrl: + process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", + apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, + }), + [], + );frontend/src/components/modules/credentials/ui/pages/CredentialsDashboardPage.tsx (1)
297-305: Move the Label import to the top with other imports.Keeps imports organized and avoids linter warnings.
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { Label } from "@/components/ui/label"; ... -// Add missing import for Label -import { Label } from "@/components/ui/label";Also applies to: 397-399
frontend/src/components/modules/marketplace/ui/components/SupplyUSDCModal.tsx (1)
5-9: Memoize ActaClient; avoid re-creating per render.Prevents unnecessary instantiation and aligns with typical client usage.
-import { useState } from "react"; +import { useMemo, useState } from "react"; ... - // Initialize Acta client (in a real app, this would come from context/config) - const actaClient = new ActaClient({ - baseUrl: process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", - apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, - }); + // Initialize Acta client (in a real app, this would come from context/config) + const actaClient = useMemo( + () => + new ActaClient({ + baseUrl: + process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", + apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, + }), + [], + );Also applies to: 31-35
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
frontend/.env.example(1 hunks)frontend/src/@types/acta.types.ts(1 hunks)frontend/src/app/dev/credential-selector/page.tsx(1 hunks)frontend/src/app/dev/credential-test/page.tsx(1 hunks)frontend/src/app/dev/credentials-dashboard/page.tsx(1 hunks)frontend/src/app/dev/modals-with-credentials/page.tsx(1 hunks)frontend/src/components/modules/credentials/hooks/useCredentialCreate.ts(1 hunks)frontend/src/components/modules/credentials/hooks/useCredentials.ts(1 hunks)frontend/src/components/modules/credentials/ui/components/CreateCredentialModal.tsx(1 hunks)frontend/src/components/modules/credentials/ui/components/CredentialCard.tsx(1 hunks)frontend/src/components/modules/credentials/ui/components/CredentialSelector.tsx(1 hunks)frontend/src/components/modules/credentials/ui/pages/CredentialsDashboardPage.tsx(1 hunks)frontend/src/components/modules/marketplace/ui/components/BorrowModal.tsx(14 hunks)frontend/src/components/modules/marketplace/ui/components/SupplyUSDCModal.tsx(4 hunks)frontend/src/lib/acta/client.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
frontend/src/components/modules/credentials/ui/components/CredentialCard.tsx (1)
frontend/src/components/modules/credentials/hooks/useCredentials.ts (3)
CredentialWithStatus(19-23)formatCredentialSummary(306-328)copyToClipboard(333-341)
frontend/src/components/modules/credentials/ui/components/CredentialSelector.tsx (2)
frontend/src/lib/acta/client.ts (1)
ActaClient(80-317)frontend/src/components/modules/credentials/hooks/useCredentials.ts (2)
CredentialWithStatus(19-23)useCredentials(126-301)
frontend/src/components/modules/credentials/ui/pages/CredentialsDashboardPage.tsx (3)
frontend/src/lib/acta/client.ts (1)
ActaClient(80-317)frontend/src/components/modules/credentials/hooks/useCredentials.ts (2)
StatusFilter(28-28)useCredentials(126-301)frontend/src/components/ui/label.tsx (1)
Label(24-24)
frontend/src/app/dev/credential-test/page.tsx (4)
frontend/src/@types/acta.types.ts (1)
LocalCredentialRecord(222-235)frontend/src/components/modules/credentials/hooks/useCredentialCreate.ts (2)
LocalCredentialRecord(21-21)getCredentialsFromLocalStorage(216-224)frontend/src/lib/acta/client.ts (1)
ActaClient(80-317)frontend/src/components/modules/credentials/ui/components/CreateCredentialModal.tsx (1)
CreateCredentialModal(170-525)
frontend/src/app/dev/modals-with-credentials/page.tsx (2)
frontend/src/components/modules/marketplace/ui/components/BorrowModal.tsx (1)
BorrowModal(40-494)frontend/src/components/modules/marketplace/ui/components/SupplyUSDCModal.tsx (1)
SupplyUSDCModal(16-268)
frontend/src/components/modules/credentials/hooks/useCredentialCreate.ts (2)
frontend/src/@types/acta.types.ts (3)
ReputationClaims(199-204)LocalCredentialRecord(222-235)PoolParticipationCredentialData(209-217)frontend/src/lib/acta/client.ts (4)
ActaClient(80-317)createCredential(151-156)CreateCredentialBody(336-336)CredentialData(337-337)
frontend/src/app/dev/credential-selector/page.tsx (3)
frontend/src/components/modules/credentials/hooks/useCredentials.ts (1)
CredentialWithStatus(19-23)frontend/src/lib/acta/client.ts (1)
ActaClient(80-317)frontend/src/components/modules/credentials/ui/components/CredentialSelector.tsx (1)
CredentialSelector(108-287)
frontend/src/app/dev/credentials-dashboard/page.tsx (2)
frontend/src/lib/acta/client.ts (1)
ActaClient(80-317)frontend/src/components/modules/credentials/ui/pages/CredentialsDashboardPage.tsx (1)
CredentialsDashboardPage(215-395)
frontend/src/lib/acta/client.ts (1)
frontend/src/@types/acta.types.ts (1)
ActaResponse(30-30)
frontend/src/components/modules/credentials/hooks/useCredentials.ts (2)
frontend/src/lib/acta/client.ts (1)
ActaClient(80-317)frontend/src/components/modules/credentials/hooks/useCredentialCreate.ts (2)
getCredentialsFromLocalStorage(216-224)removeCredentialFromLocalStorage(229-244)
frontend/src/@types/acta.types.ts (2)
frontend/src/lib/acta/client.ts (1)
ActaResponse(335-335)frontend/src/components/modules/credentials/hooks/useCredentialCreate.ts (1)
LocalCredentialRecord(21-21)
frontend/src/components/modules/credentials/ui/components/CreateCredentialModal.tsx (3)
frontend/src/lib/acta/client.ts (2)
ActaClient(80-317)createCredential(151-156)frontend/src/@types/acta.types.ts (4)
ParticipationDuration(172-172)CredentialRiskLevel(177-177)PerformanceTier(182-185)PoolTypeExperience(190-194)frontend/src/components/modules/credentials/hooks/useCredentialCreate.ts (2)
useCredentialCreate(100-211)CredentialCreateFormData(26-30)
frontend/src/components/modules/marketplace/ui/components/SupplyUSDCModal.tsx (5)
frontend/src/components/modules/credentials/hooks/useCredentials.ts (1)
CredentialWithStatus(19-23)frontend/src/components/modules/marketplace/hooks/useWalletBalance.hook.ts (1)
useWalletBalance(11-66)frontend/src/lib/acta/client.ts (1)
ActaClient(80-317)frontend/src/components/modules/credentials/ui/components/CredentialSelector.tsx (1)
CredentialSelector(108-287)frontend/src/components/modules/marketplace/hooks/useSupply.hook.ts (1)
useSupply(21-235)
frontend/src/components/modules/marketplace/ui/components/BorrowModal.tsx (4)
frontend/src/helpers/health-factor.helper.ts (1)
HealthFactorResult(7-16)frontend/src/components/modules/credentials/hooks/useCredentials.ts (1)
CredentialWithStatus(19-23)frontend/src/lib/acta/client.ts (1)
ActaClient(80-317)frontend/src/components/modules/credentials/ui/components/CredentialSelector.tsx (1)
CredentialSelector(108-287)
🔇 Additional comments (8)
frontend/src/components/modules/credentials/ui/components/CreateCredentialModal.tsx (1)
217-221: Confirm expirationDate format expected by ACTA API.Input provides "YYYY-MM-DD". If ACTA expects ISO 8601, convert before sending (e.g.,
new Date(value).toISOString()).Would you like me to patch conversion to ISO in
onSubmitonce you confirm the expected format?frontend/src/components/modules/marketplace/ui/components/BorrowModal.tsx (1)
52-56: Public API key usage — verify intent.Using
NEXT_PUBLIC_ACTA_API_KEYexposes the key to all clients. Confirm the ACTA API key is safe to be public; if not, use a server-side proxy or user-provided key stored client-side (not bundled).frontend/src/app/dev/credential-test/page.tsx (1)
27-31: Public API key usage — verify intent (dev page).Same concern as BorrowModal:
NEXT_PUBLIC_ACTA_API_KEYis exposed. Confirm this is safe for ACTA or switch to a safer approach.frontend/src/components/modules/credentials/hooks/useCredentials.ts (1)
109-116: Confirm correct HTTP method and path for credential status update
PR description mentions PATCH /credentials/:contractId/status but the client calls PUT /credentials/{id}/status; please share the ACTA OpenAPI snippet or API docs for this endpoint so we can verify the exact HTTP verb and URL format.frontend/src/components/modules/marketplace/ui/components/SupplyUSDCModal.tsx (1)
126-159: Good, optional credential UX is non-blocking.Selector wiring and benefits hint look correct and match the privacy-preserving goal.
frontend/src/@types/acta.types.ts (2)
361-367: No JSON serialization of POOL_DURATION_DAYS detected Infinity is only used in-memory and never persisted; no changes required.
334-356: No duplicate Acta type definitions found
AllActaOk,ActaErr, andActaResponseare declared only infrontend/src/@types/acta.types.ts.frontend/src/app/dev/modals-with-credentials/page.tsx (1)
250-256: Do not removepoolDataprop
BorrowModalPropsincludespoolData: PoolData | null, so passingpoolData={mockPoolData}is valid.Likely an incorrect or invalid review comment.
| # Acta | ||
| NEXT_PUBLIC_ACTA_API_URL=https://acta.up.railway.app | ||
| NEXT_PUBLIC_ACTA_DEFAULT_NETWORK=testnet | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
Add API key sample and warn about client-exposed secrets.
- .env.example is missing NEXT_PUBLIC_ACTA_API_KEY, yet code reads it in multiple places. Add it to guide setup.
- If the ACTA API key is sensitive, avoid exposing it via NEXT_PUBLIC and route via a backend proxy. If it’s intended to be public (rate-limited per-user), document that explicitly.
Apply this diff:
# Acta
NEXT_PUBLIC_ACTA_API_URL=https://acta.up.railway.app
NEXT_PUBLIC_ACTA_DEFAULT_NETWORK=testnet
+NEXT_PUBLIC_ACTA_API_KEY=To confirm usage and avoid dead vars, search for references:
🏁 Script executed:
#!/bin/bash
rg -n -C2 'NEXT_PUBLIC_ACTA_DEFAULT_NETWORK|NEXT_PUBLIC_ACTA_API_KEY|NEXT_PUBLIC_ACTA_API_URL'Length of output: 3628
Add NEXT_PUBLIC_ACTA_API_KEY to .env.example and warn about public exposure
# Acta
NEXT_PUBLIC_ACTA_API_URL=https://acta.up.railway.app
NEXT_PUBLIC_ACTA_DEFAULT_NETWORK=testnet
+# WARNING: NEXT_PUBLIC_ keys are exposed to the browser. If this API key is sensitive, proxy requests through a backend; if it’s intended to be public, document rate limits and usage.
NEXT_PUBLIC_ACTA_API_KEY=📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Acta | |
| NEXT_PUBLIC_ACTA_API_URL=https://acta.up.railway.app | |
| NEXT_PUBLIC_ACTA_DEFAULT_NETWORK=testnet | |
| # Acta | |
| NEXT_PUBLIC_ACTA_API_URL=https://acta.up.railway.app | |
| NEXT_PUBLIC_ACTA_DEFAULT_NETWORK=testnet | |
| # WARNING: NEXT_PUBLIC_ keys are exposed to the browser. If this API key is sensitive, proxy requests through a backend; if it’s intended to be public, document rate limits and usage. | |
| NEXT_PUBLIC_ACTA_API_KEY= |
🤖 Prompt for AI Agents
In frontend/.env.example around lines 9 to 12, the example file is missing
NEXT_PUBLIC_ACTA_API_KEY; add a line defining
NEXT_PUBLIC_ACTA_API_KEY=your_api_key_here to document the key usage, and add a
brief comment above it warning that NEXT_PUBLIC_* variables are exposed to the
browser and should only contain public-safe values or limited-scope API keys;
ensure the example uses a placeholder value and the warning text is concise.
| const response = await client.createCredential(requestBody); | ||
|
|
||
| if (!response.success) { | ||
| throw new Error(response.error || "Failed to create credential"); | ||
| } | ||
| console.log("response", response); | ||
| // The API response has nested data structure: response.data.data | ||
| const responseData = response.data as unknown as { | ||
| data: CredentialData; | ||
| }; | ||
| const credentialResponse = responseData.data; | ||
|
|
||
| const result: CredentialCreateResult = { | ||
| contractId: credentialResponse.contractId, | ||
| hash: credentialResponse.hash, | ||
| transactionHash: credentialResponse.transactionHash, | ||
| createdAt: credentialResponse.createdAt, | ||
| ledgerSequence: credentialResponse.ledgerSequence, | ||
| }; |
There was a problem hiding this comment.
Fix response shape handling (no nested data) and remove debug log.
ActaClient.createCredential returns ActaResponse<CredentialData>, so response.data is already the credential. Accessing response.data.data will yield undefined. Also, drop the stray console.log.
Apply:
- console.log("response", response);
- // The API response has nested data structure: response.data.data
- const responseData = response.data as unknown as {
- data: CredentialData;
- };
- const credentialResponse = responseData.data;
+ // ActaClient.createCredential returns ActaResponse<CredentialData>
+ const credentialResponse = response.data as CredentialData;
+ if (!credentialResponse || !credentialResponse.contractId) {
+ throw new Error("Malformed ACTA response: missing contractId");
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const response = await client.createCredential(requestBody); | |
| if (!response.success) { | |
| throw new Error(response.error || "Failed to create credential"); | |
| } | |
| console.log("response", response); | |
| // The API response has nested data structure: response.data.data | |
| const responseData = response.data as unknown as { | |
| data: CredentialData; | |
| }; | |
| const credentialResponse = responseData.data; | |
| const result: CredentialCreateResult = { | |
| contractId: credentialResponse.contractId, | |
| hash: credentialResponse.hash, | |
| transactionHash: credentialResponse.transactionHash, | |
| createdAt: credentialResponse.createdAt, | |
| ledgerSequence: credentialResponse.ledgerSequence, | |
| }; | |
| const response = await client.createCredential(requestBody); | |
| if (!response.success) { | |
| throw new Error(response.error || "Failed to create credential"); | |
| } | |
| // ActaClient.createCredential returns ActaResponse<CredentialData> | |
| const credentialResponse = response.data as CredentialData; | |
| if (!credentialResponse || !credentialResponse.contractId) { | |
| throw new Error("Malformed ACTA response: missing contractId"); | |
| } | |
| const result: CredentialCreateResult = { | |
| contractId: credentialResponse.contractId, | |
| hash: credentialResponse.hash, | |
| transactionHash: credentialResponse.transactionHash, | |
| createdAt: credentialResponse.createdAt, | |
| ledgerSequence: credentialResponse.ledgerSequence, | |
| }; |
🤖 Prompt for AI Agents
frontend/src/components/modules/credentials/hooks/useCredentialCreate.ts around
lines 141 to 159: the code incorrectly treats response.data as nested
(response.data.data) and leaves a stray console.log; change handling so that
response.data is used directly as the CredentialData (e.g., const
credentialResponse = response.data as CredentialData), remove the
console.log("response", response), and then build the CredentialCreateResult
from credentialResponse fields as before.
| async function fetchCredentialStatus( | ||
| client: ActaClient, | ||
| contractId: string, | ||
| ): Promise<"Active" | "Revoked" | "Suspended"> { | ||
| try { | ||
| const response = await client.getByContractId(contractId); | ||
|
|
||
| if (!response.success) { | ||
| console.warn(`Failed to fetch status for ${contractId}:`, response.error); | ||
| return "Active"; // Default to Active if we can't fetch | ||
| } | ||
|
|
||
| // Assuming the API returns a status field | ||
| const credentialData = response.data as { | ||
| status?: string; | ||
| credential?: { status?: string }; | ||
| }; | ||
| const status = | ||
| credentialData?.status || credentialData?.credential?.status || "Active"; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Align status parsing with the client’s typed response.
ActaClient.getByContractId returns ActaResponse<CredentialData>; prefer using that type instead of ad-hoc shapes. This reduces brittleness if the API doesn’t return credential.status.
-import { ActaClient } from "@/lib/acta/client";
+import { ActaClient } from "@/lib/acta/client";
+// Optionally import the type if exported:
+// import type { CredentialData } from "@/lib/acta/client";
...
- // Assuming the API returns a status field
- const credentialData = response.data as {
- status?: string;
- credential?: { status?: string };
- };
- const status =
- credentialData?.status || credentialData?.credential?.status || "Active";
+ // Prefer a single source of truth; fall back conservatively if absent
+ const raw = response.data as any; // or as CredentialData if available
+ const status: string =
+ raw?.status ?? raw?.credential?.status ?? "Active";Run to confirm the actual shape of CredentialData:
🏁 Script executed:
#!/bin/bash
# Find the CredentialData definition and its fields
rg -nP -C2 '(^\s*(export\s+)?(interface|type)\s+CredentialData\b)|\bCredentialData\b'Length of output: 3370
🏁 Script executed:
#!/bin/bash
# Print full CredentialData interface with context
rg -nP -C10 'interface\s+CredentialData\b' frontend/src/lib/acta/client.tsLength of output: 429
🏁 Script executed:
#!/bin/bash
# Show ActaResponse interface
rg -nP -C10 'interface\s+ActaResponse\b' frontend/src/lib/acta/client.ts
# Show getByContractId method signature
rg -nP -C5 'getByContractId' -n frontend/src/lib/acta/client.tsLength of output: 831
Use a dedicated status response type instead of ad-hoc casts
The CredentialData interface only includes { contractId, hash, transactionHash, createdAt, ledgerSequence } and doesn’t define any status field. Extract the actual API shape for credential status (top-level status or nested credential.status), update or extend CredentialData (or create a new CredentialStatusResponse) in client.ts, adjust getByContractId’s return type accordingly, then import and use that type here for safe, type-checked status parsing.
🤖 Prompt for AI Agents
In frontend/src/components/modules/credentials/hooks/useCredentials.ts around
lines 68 to 87, the code uses an ad-hoc type cast to read status from the API
response; instead declare a proper response type in client.ts (e.g.,
CredentialStatusResponse with fields for top-level status and nested
credential.status), update getByContractId to return that typed response (or
extend existing CredentialData), then import and use the new type here to access
response.data.status and response.data.credential.status without casts and with
TypeScript-checked fallbacks to "Active".
| Created {new Date(createdAt).toLocaleDateString()} | ||
| </p> |
There was a problem hiding this comment.
Guard against invalid createdAt date.
If createdAt is malformed, this renders "Invalid Date". Add a safe fallback.
Apply this diff:
- <p className="text-sm text-gray-400">
- Created {new Date(createdAt).toLocaleDateString()}
- </p>
+ <p className="text-sm text-gray-400">
+ Created {Number.isNaN(new Date(createdAt).getTime()) ? "Unknown" : new Date(createdAt).toLocaleDateString()}
+ </p>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Created {new Date(createdAt).toLocaleDateString()} | |
| </p> | |
| <p className="text-sm text-gray-400"> | |
| Created {Number.isNaN(new Date(createdAt).getTime()) ? "Unknown" : new Date(createdAt).toLocaleDateString()} | |
| </p> |
🤖 Prompt for AI Agents
In frontend/src/components/modules/credentials/ui/components/CredentialCard.tsx
around lines 141-142, the code renders Created {new
Date(createdAt).toLocaleDateString()} which can produce "Invalid Date" for
malformed createdAt values; guard by parsing createdAt into a Date, check
validity (e.g., const d = new Date(createdAt); if Number.isNaN(d.getTime()) )
and render a safe fallback like "Unknown date" or an empty string instead of
toLocaleDateString(), ensuring you only call toLocaleDateString() for valid Date
objects.
| // Initialize Acta client (in a real app, this would come from context/config) | ||
| const actaClient = new ActaClient({ | ||
| baseUrl: process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", | ||
| apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, | ||
| }); |
There was a problem hiding this comment.
ActaClient recreated on every render → can trigger repeated refreshes in CredentialSelector.
Passing a fresh client each render causes the child hook to recreate callbacks/effects, potentially reloading credentials frequently.
Apply this diff to memoize the client:
-import { useEffect, useState } from "react";
+import { useEffect, useMemo, useState } from "react";- const actaClient = new ActaClient({
- baseUrl: process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001",
- apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY,
- });
+ const actaClient = useMemo(
+ () =>
+ new ActaClient({
+ baseUrl: process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001",
+ apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY,
+ }),
+ [],
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Initialize Acta client (in a real app, this would come from context/config) | |
| const actaClient = new ActaClient({ | |
| baseUrl: process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", | |
| apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, | |
| }); | |
| // <— at the top of the file | |
| import { useEffect, useMemo, useState } from "react"; | |
| // … later in your component … | |
| // Initialize Acta client (in a real app, this would come from context/config) | |
| - const actaClient = new ActaClient({ | |
| - baseUrl: process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", | |
| - apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, | |
| const actaClient = useMemo( | |
| () => | |
| new ActaClient({ | |
| baseUrl: process.env.NEXT_PUBLIC_ACTA_API_URL || "http://localhost:3001", | |
| apiKey: process.env.NEXT_PUBLIC_ACTA_API_KEY, | |
| }), | |
| [], | |
| ); |
🤖 Prompt for AI Agents
In frontend/src/components/modules/marketplace/ui/components/BorrowModal.tsx
around lines 52-56, the ActaClient is instantiated on every render which causes
child components/hooks (like CredentialSelector) to recreate callbacks/effects
and repeatedly refresh credentials; fix this by memoizing the ActaClient
instance (e.g., using React.useMemo with the env vars as dependencies) or by
creating the client outside the component so the same instance is reused across
renders, ensuring you only recreate the client when the baseUrl or apiKey
actually change.
| if (response.ok) { | ||
| return { | ||
| success: true, | ||
| data: responseData as T, | ||
| }; |
There was a problem hiding this comment.
Fix the ACTA response wrapping bug
responseData already comes back in the { success, data|error } envelope the rest of the code expects. Re-wrapping it under a second { success: true, data: ... } shell means callers end up with result.data.success instead of the credential payload, so status checks and dashboard flows won’t work. Please return the parsed ACTA envelope directly.
- if (response.ok) {
- return {
- success: true,
- data: responseData as T,
- };
+ if (response.ok) {
+ const actaResponse = responseData as ActaResponse<T>;
+
+ if (
+ actaResponse &&
+ typeof actaResponse === "object" &&
+ "success" in actaResponse
+ ) {
+ return actaResponse;
+ }
+
+ return {
+ success: true,
+ data: responseData as T,
+ };
} else {🤖 Prompt for AI Agents
In frontend/src/lib/acta/client.ts around lines 279 to 283, the code incorrectly
re-wraps the parsed responseData (which is already in the { success, data|error
} envelope) into a new { success: true, data: responseData } object; remove that
re-wrap and instead return the parsed responseData directly so callers receive
the original ACTA envelope (preserving responseData.success and
responseData.data or error) and adjust the return path to return responseData as
the function result.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
frontend/src/app/dev/modals-with-credentials/page.tsx (3)
23-37: Consider extracting mock data to a separate constants fileThe mock pool data is hardcoded within the component. For better maintainability and reusability across test pages, consider moving this to a shared constants or fixtures file.
Consider creating a new file
frontend/src/fixtures/pool.fixtures.ts:export const MOCK_POOL_DATA = { name: "USDC Pool", totalSupplied: "2,400,000", totalBorrowed: "1,800,000", utilizationRate: "75%", reserves: [ { symbol: "USDC", supplied: "2,400,000", borrowed: "1,800,000", supplyAPY: "12.5", borrowAPY: "15.2", }, ], };Then import and use it in the component:
+import { MOCK_POOL_DATA } from "@/fixtures/pool.fixtures"; export default function ModalsWithCredentialsPage() { const [isBorrowModalOpen, setIsBorrowModalOpen] = useState(false); const [isSupplyModalOpen, setIsSupplyModalOpen] = useState(false); - // Mock pool data for demonstration - const mockPoolData = { - name: "USDC Pool", - totalSupplied: "2,400,000", - totalBorrowed: "1,800,000", - utilizationRate: "75%", - reserves: [ - { - symbol: "USDC", - supplied: "2,400,000", - borrowed: "1,800,000", - supplyAPY: "12.5", - borrowAPY: "15.2", - }, - ], - }; + const mockPoolData = MOCK_POOL_DATA;
233-239: Use semantic HTML for better quotesThe quoted text in lines 233-234 and 237-238 uses regular double quotes. Consider using HTML entities or semantic quotes for better typography.
Apply this diff for better typography:
- • <strong>Borrow Modal:</strong> "You may qualify for - better interest rates" + • <strong>Borrow Modal:</strong> "You may qualify for + better interest rates" </li> <li> - • <strong>Supply Modal:</strong> "You may qualify for - higher APY and reduced fees" + • <strong>Supply Modal:</strong> "You may qualify for + higher APY and reduced fees"
1-269: Consider adding error boundaries for modal componentsSince this is a test page demonstrating modal integration, consider wrapping the modal components in error boundaries to catch and display any integration issues gracefully during testing.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
frontend/src/app/dev/modals-with-credentials/page.tsx(1 hunks)frontend/src/components/modules/credentials/ui/components/CredentialSelector.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/modules/credentials/ui/components/CredentialSelector.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/app/dev/modals-with-credentials/page.tsx (2)
frontend/src/components/modules/marketplace/ui/components/BorrowModal.tsx (1)
BorrowModal(28-283)frontend/src/components/modules/marketplace/ui/components/SupplyUSDCModal.tsx (1)
SupplyUSDCModal(11-202)
| <ul className="space-y-1 text-sm text-gray-400"> | ||
| <li> | ||
| • <strong>Non-intrusive:</strong> Credential selection is | ||
| optional and doesn't block core flows |
There was a problem hiding this comment.
Escape the apostrophe correctly in the JSX text
Line 210 uses ' to escape the apostrophe, but this should be consistent with other apostrophe usage in the file (e.g., line 243).
Apply this diff to fix the inconsistency:
- • <strong>Non-intrusive:</strong> Credential selection is
- optional and doesn't block core flows
+ • <strong>Non-intrusive:</strong> Credential selection is
+ optional and doesn't block core flows📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| optional and doesn't block core flows | |
| • <strong>Non-intrusive:</strong> Credential selection is | |
| optional and doesn't block core flows |
🤖 Prompt for AI Agents
In frontend/src/app/dev/modals-with-credentials/page.tsx at line 210, the JSX
text uses ' to escape the apostrophe which is inconsistent with other lines
(e.g., 243); replace ' with the same apostrophe style used elsewhere (use a
plain single quote character in the JSX text or the same {"'"} interpolation
pattern used in the file) so the text reads "optional and doesn't block core
flows" and matches the file's apostrophe convention.
| isOpen={isBorrowModalOpen} | ||
| onClose={() => setIsBorrowModalOpen(false)} | ||
| poolData={mockPoolData} | ||
| poolId="test-pool" | ||
| /> |
There was a problem hiding this comment.
Fix the type mismatch for BorrowModal props
The BorrowModal component expects a poolId prop (string or undefined) but is receiving both poolData and poolId. The poolData prop is not defined in the BorrowModalProps interface.
Apply this diff to fix the prop mismatch:
- <BorrowModal
- isOpen={isBorrowModalOpen}
- onClose={() => setIsBorrowModalOpen(false)}
- poolData={mockPoolData}
- poolId="test-pool"
- />
+ <BorrowModal
+ isOpen={isBorrowModalOpen}
+ onClose={() => setIsBorrowModalOpen(false)}
+ poolId="test-pool"
+ />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| isOpen={isBorrowModalOpen} | |
| onClose={() => setIsBorrowModalOpen(false)} | |
| poolData={mockPoolData} | |
| poolId="test-pool" | |
| /> | |
| <BorrowModal | |
| isOpen={isBorrowModalOpen} | |
| onClose={() => setIsBorrowModalOpen(false)} | |
| poolId="test-pool" | |
| /> |
🤖 Prompt for AI Agents
In frontend/src/app/dev/modals-with-credentials/page.tsx around lines 252 to
256, the BorrowModal is being passed a poolData prop that is not declared on
BorrowModalProps; either remove the extraneous poolData prop from the JSX so
only poolId (string | undefined) is passed, or update the BorrowModalProps
interface to include an optional poolData property with the correct type
(matching mockPoolData) and adjust the BorrowModal component to accept/use it
consistently.
Pull Request for TrustBridge - Close Issue
❗ Pull Request Information
This PR integrates ACTA (Verifiable Credentials) into TrustBridge to enable Pool Participation Credentials with a privacy-first approach (no wallet addresses, no exact amounts). It is a frontend-only integration using a small typed HTTP client configured via
NEXT_PUBLIC_ACTA_API_URL.Goals
🌀 Summary of Changes
Config
frontend/.env.example: addNEXT_PUBLIC_ACTA_API_URL=https://acta.up.railway.app.Typed HTTP Client (no provider)
src/@types/acta.types.ts:ActaOk/ActaErr,ActaHealth,CreateCredentialReq/Res,GetCredentialRes, andPoolParticipationClaims.src/lib/acta/client.ts:ping,health,createCredential,getByContractId,getByHash,updateStatus.Always returns
{ success, data|error }(no thrown exceptions), ready to acceptAuthorization/X-API-Keyheaders in the future.Health Check (dev tool)
src/components/dev/ActaHealthCard.tsx: “Check ACTA health” button, status badge, collapsible JSON.Credentials (creation + management)
src/components/modules/credentials/ui/components/CreateCredentialModal.tsxForm (react-hook-form + ShadCN) for:
participationDuration,riskLevel,performanceTier,poolTypeExperience[]issuer,issuanceDate(default now), optionalexpirationDatesrc/components/modules/credentials/ui/pages/CredentialsDashboardPage.tsx: list, filters by status, empty state.src/components/modules/credentials/ui/components/CredentialCard.tsx: summary, status badge, actions (Share, Update status).src/components/modules/credentials/hooks/useCredentialCreate.ts&useCredentials.ts: wrap client calls + local cache.src/lib/credentials/local-index.ts: lightweight persistence inlocalStorage['tb_vc_index_v1'](only non-sensitive display data + IDs).Status Update & Share
PATCH /credentials/:contractId/statuswith optimistic UI + rollback on error.contractId/hash).Pool Touchpoints
BorrowerPoolPage.tsx&LenderPoolPage.tsx: non-blocking “Your verifiable reputation” card/pill that opens Dashboard/Create.Docs & Tests
client.tsand hooks (mockedfetch); basic e2e happy path (Health → Create → List → Update → Share).🛠 Testing
Evidence Before Solution
Evidence After Solution
Manual Test Steps
NEXT_PUBLIC_ACTA_API_URL(default prod URL is fine).contractId/hash.✅ ESLint Compliance (Mandatory)
To ensure that the code follows project standards, please run the following command and attach a screenshot of the output:
You should see:
📸 Attach a screenshot showing the result of the lint check:

📂 Related Issue
This pull request will close #264 upon merging.
🎉 Thank you for reviewing this PR! 🎉
Summary by CodeRabbit
New Features
UI/UX
Chores