flexprice/flexprice-front
add metadata display section in InvoiceDetail
Open
#370 opened on Sep 18, 2025
good first issuehacktoberfestjavascriptreact
Repository metrics
- Stars
- (32 stars)
- PR merge metrics
- (PR metrics pending)
Description
dd a dedicated Metadata section to the InvoiceDetail view/component to display invoice metadata (arbitrary key/value pairs).
This improves visibility and UX for billing-related metadata such as order ids, external references, or notes.
Goals
- Show invoice metadata clearly in the Invoice detail page.
- Provide copy-to-clipboard support for values.
- Handle empty, long, or sensitive metadata gracefully.
Acceptance Criteria
-
Section placement
- Appears in
InvoiceDetailbelow invoice header (or sidebar if layout allows). - Title: Metadata.
- Appears in
-
Data
- Source:
invoice.metadata(Record<string, string | number | null>). - Show
No metadata availableif empty.
- Source:
-
Rendering
- Two-column layout: key (left), value (right).
- Humanize keys (e.g.,
external_order_id→External order id). - Truncate long values; show full value in tooltip + allow copy.
-
Interactions
- Copy button for each value → copies full string to clipboard.
- Tooltip on hover shows full value.
- Accessible via keyboard with aria-labels.
-
Styling
- Use Tailwind + shadcn/ui.
- Compact, scannable layout with consistent spacing.
- Keys column fixed width; values truncated or wrapped.
-
Edge cases
- Null values → render as empty string.
- Very long values → truncate + tooltip.
- Sensitive keys (
token,secret,password,cvv,ssn) → mask value withHidden for security.
-
Tests
- Renders metadata list.
- Shows “No metadata available” when empty.
- Copy button copies correct value.
- Sensitive keys are masked.
Implementation Notes
- Create reusable
InvoiceMetadatacomponent. - Input:
metadata?: Record<string, unknown>. - Isolated so it can be reused elsewhere.
Minimal Example (React + TS):
function humanizeKey(k: string) {
return k.replace(/[_-]/g, " ").replace(/\b\w/g, c => c.toUpperCase());
}
function isSensitive(key: string) {
return /(token|secret|password|ssn|cvv)/i.test(key);
}