diff --git a/CHANGELOG.md b/CHANGELOG.md index 36ad11a..5c73a09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to Folio will be documented in this file. +## [0.2.3.1] - 2026-04-04 + +### Changed +- Card detail page redesigned with balance as hero element, Apple Card-style quick actions, and lending mechanics hidden in expandable "How it works" section +- Card list items no longer show settlement dates, keeping the wallet feel clean +- Spend flow card-mode copy softened: "Get Card" header, "No interest, no fees" messaging, simpler CTA +- "Collateral" label replaced with "Backed by" across all card surfaces for consistent wallet-first language + +### Fixed +- "How it works" section now uses past tense for settled and expired cards + ## [0.2.3.0] - 2026-04-04 ### Added diff --git a/VERSION b/VERSION index 517717a..10be6dc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.3.0 +0.2.3.1 diff --git a/src/components/CardDetail.tsx b/src/components/CardDetail.tsx index 0bdbf16..0c7cb2f 100644 --- a/src/components/CardDetail.tsx +++ b/src/components/CardDetail.tsx @@ -32,6 +32,7 @@ export default function CardDetail({ noteId, onBack }: CardDetailProps) { const [loading, setLoading] = useState(true); const [freezing, setFreezing] = useState(false); const [cardFrozen, setCardFrozen] = useState(false); + const [showDetails, setShowDetails] = useState(false); useEffect(() => { const fetchNote = async () => { @@ -81,14 +82,18 @@ export default function CardDetail({ noteId, onBack }: CardDetailProps) { -
+
-
-
-
-
+
+
+
+
+
+
+
+
); } @@ -103,43 +108,65 @@ export default function CardDetail({ noteId, onBack }: CardDetailProps) { } const isActive = note.status === 'active'; + const remainingBalance = note.amount; + const created = new Date(note.createdAt); const expiry = new Date(note.expiryDate); - const remainingBalance = note.amount; // In production, subtract total spent - const spendLimitDisplay = note.cardSpendLimit - ? formatUsd(note.cardSpendLimit / 100) - : formatUsd(note.amount); return (
- {/* Header */} -
+ {/* Header — minimal */} +
-
Card Details
+
+ •••• {note.cardLastFour} +
+ {!isActive && ( + + {note.status === 'repaid' ? 'Settled' : 'Expired'} + + )}
- {/* Full Card Visual */} + {/* Balance — the hero */} +
+
+ {formatUsd(remainingBalance)} +
+
+ {cardFrozen ? 'Card frozen' : isActive ? 'Available balance' : `${note.status === 'repaid' ? 'Settled' : 'Expired'}`} +
+
+ + {/* Card Visual */}
- {/* Card shine */} + {/* Shine */}
@@ -159,149 +186,166 @@ export default function CardDetail({ noteId, onBack }: CardDetailProps) { {/* Top row */}
-
Folio
-
Prepaid Card
+
Folio
-
VISA
+
VISA
{/* Chip */} -
-
+
{/* Card Number */} -
-
+
- {'•••• •••• •••• '}{note.cardLastFour} + •••• •••• •••• {note.cardLastFour}
- {/* Bottom row */} -
-
-
-
Status
-
- {cardFrozen ? 'Frozen' : isActive ? 'Active' : note.status} -
-
-
-
Source
-
{note.symbol}
-
-
-
-
Balance
-
- {formatUsd(remainingBalance)} -
+ {/* Bottom */} +
+
+ {note.symbol}
- {/* Quick Actions */} + {/* Quick Actions — Apple Card style */} {isActive && ( -
+
)} - {/* Card Info */} + {/* Card Stats — simple, no lending jargon */}
-
- Card Info -
-
- {[ - { label: 'Spend Limit', value: spendLimitDisplay }, - { label: 'Remaining', value: formatUsd(remainingBalance), accent: true }, - { label: 'Collateral', value: `${note.shares.toFixed(4)} ${note.symbol}` }, - { label: 'Interest', value: '0%', accent: true }, - { label: 'Fees', value: '$0', accent: true }, - ].map((row) => ( -
- {row.label} - {row.value} -
- ))} +
+
+ Backed by + {note.symbol} +
+
+ Loaded + + {created.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} + +
+
+ Interest + 0% +
+
+ Monthly fee + $0 +
- {/* Collar Details */} -
-
- Protection Range -
-
- {[ - { label: 'Price at Issue', value: formatUsd(note.stockPrice) }, - { label: 'Floor', value: formatUsd(note.floor) }, - { label: 'Cap', value: formatUsd(note.cap) }, - { label: 'Duration', value: `${note.durationMonths} month${note.durationMonths > 1 ? 's' : ''}` }, - { label: 'Settle by', value: expiry.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) }, - ].map((row) => ( -
- {row.label} - {row.value} + {/* Settle reminder — soft, not aggressive */} + {isActive && ( +
+
+
+ + + +
+
+
+ Settle by {expiry.toLocaleDateString('en-US', { month: 'long', day: 'numeric' })} +
+
+ Pay back to unlock your {note.symbol} shares. +
- ))} +
-
+ )} - {/* Settle info */} -
- Settle by {expiry.toLocaleDateString('en-US', { month: 'long', day: 'numeric' })} to unlock your {note.symbol} shares. -
+ {/* Expandable details — tuck the lending mechanics away */} + + + {showDetails && ( +
+
+ {isActive + ? `Your card is backed by ${note.shares.toFixed(2)} shares of ${note.symbol}. We protect these shares with a zero-cost collar so your downside is limited. Pay back anytime to release your shares.` + : `This card was backed by ${note.shares.toFixed(2)} shares of ${note.symbol}, protected with a zero-cost collar.`} +
+
+ {[ + { label: 'Shares held', value: `${note.shares.toFixed(4)} ${note.symbol}` }, + { label: 'Price at load', value: formatUsd(note.stockPrice) }, + { label: 'Protected range', value: `${formatUsd(note.floor)} – ${formatUsd(note.cap)}` }, + ].map((row) => ( +
+ {row.label} + + {row.value} + +
+ ))} +
+
+ )}
); } diff --git a/src/components/CardResult.tsx b/src/components/CardResult.tsx index b9ad1a6..946dfb8 100644 --- a/src/components/CardResult.tsx +++ b/src/components/CardResult.tsx @@ -129,12 +129,12 @@ export default function CardResult({ result, onViewNote, onViewCards, onViewCard
- {/* Advance Summary */} + {/* Card Summary */}
{[ { label: 'Type', value: 'Prepaid Visa' }, - { label: 'Collateral', value: `${formatShares(result.shares)} ${result.symbol}` }, + { label: 'Backed by', value: `${formatShares(result.shares)} ${result.symbol}` }, { label: 'Interest', value: '0%', accent: true }, { label: 'Fees', value: '$0', accent: true }, ].map((row) => ( diff --git a/src/components/CardsList.tsx b/src/components/CardsList.tsx index d8352b4..ade7352 100644 --- a/src/components/CardsList.tsx +++ b/src/components/CardsList.tsx @@ -109,7 +109,6 @@ export default function CardsList({ onGetCard, onSelectCard }: CardsListProps) { {activeCards.map((card) => { const config = statusConfig[card.status]; const created = new Date(card.createdAt); - const expiry = new Date(card.expiryDate); const isFrozen = card.cardState === 'PAUSED'; return ( @@ -162,7 +161,7 @@ export default function CardsList({ onGetCard, onSelectCard }: CardsListProps) { Folio Card •••• {card.cardLastFour}
- {card.symbol} · {created.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} · Settle by {expiry.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} + {card.symbol} · {created.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
diff --git a/src/components/SpendFlow.tsx b/src/components/SpendFlow.tsx index d8278a7..6725422 100644 --- a/src/components/SpendFlow.tsx +++ b/src/components/SpendFlow.tsx @@ -242,7 +242,7 @@ export default function SpendFlow({ mode, selectedHolding, holdings, prices, cur -
{mode === 'send' ? 'Send Payment' : 'Load Card'}
+
{mode === 'send' ? 'Send Payment' : 'Get Card'}
{/* Recipient (send mode only) */} @@ -411,7 +411,7 @@ export default function SpendFlow({ mode, selectedHolding, holdings, prices, cur
-
0% interest loan from your portfolio
+
{mode === 'send' ? '0% interest from your portfolio' : 'No interest, no fees'}
{/* Stat Grid */} @@ -435,7 +435,7 @@ export default function SpendFlow({ mode, selectedHolding, holdings, prices, cur {/* Duration Picker */}
- Repay within + {mode === 'send' ? 'Repay within' : 'Duration'}
{[1, 2, 3].map((m) => { @@ -486,8 +486,11 @@ export default function SpendFlow({ mode, selectedHolding, holdings, prices, cur {/* Repayment Note */}
- Repay by {formatDate(collar.expiryDate)} to - unlock your shares. If not repaid, shares are sold to settle. + {mode === 'send' ? ( + <>Repay by {formatDate(collar.expiryDate)} to unlock your shares. If not repaid, shares are sold to settle. + ) : ( + <>Pay back by {formatDate(collar.expiryDate)} to release your shares. No interest, no fees, ever. + )}
{/* How does this work? */} @@ -552,7 +555,7 @@ export default function SpendFlow({ mode, selectedHolding, holdings, prices, cur : sendStatus === 'signing' ? 'Signing...' : sendStatus === 'submitting' ? 'Submitting...' : 'Processing...') - : (mode === 'send' ? `Send ${formatUsd(val)}` : `Get Card · ${formatUsd(val)} at 0%`)} + : (mode === 'send' ? `Send ${formatUsd(val)}` : `Get ${formatUsd(val)} Card`)}
);