| noteId | 26067490b54a11f0ad8f4f365506ecb8 |
|---|---|
| tags |
Implemented full library borrowing system with:
- Borrow books from library pool
- Return books to library pool
- Track active borrows with expiry time
- Display expiry countdown on book cards
- Shows red "Not Available" badge when all books are borrowed
- Shows green "Available" badge when books are available
- Located at top-right corner of book card
- Uses
borrowFromPool(tokenId)from library pool contract - One book per user per title
- Automatically assigns rental period (default: 3 days)
- Returns
recordIdfor tracking
- Uses
returnMyBorrow(tokenId)from library pool contract - User can return before expiry
- Auto-deleted when expired
- Uses
getActiveBorrows(address user)to fetch all user's active borrows - Returns array of
BorrowView:recordId: Unique borrow record identifiertokenId: Book IDexpiry: Unix timestamp when borrow expires
- Shows countdown timer: "2d 5h left", "3h 45m left", or "30m left"
- Blue badge under availability info
- Updates based on expiry timestamp from contract
- Shows "Expired" when time runs out
// Get all active borrows for a user
getActiveBorrows(address user)
→ returns BorrowView[] { recordId, tokenId, expiry }
// Check if user can use this book (has active borrow)
usableBalanceOf(address user, uint256 tokenId)
→ returns uint256 (1 if borrowed, 0 if not)
// Get available books (total - borrowed)
previewAvailability(uint256 tokenId)
→ returns (uint256 available, uint256 frozenNow)// Borrow a book
borrowFromPool(uint256 tokenId)
→ returns uint256 recordId
// Return borrowed book
returnMyBorrow(uint256 tokenId)
→ void// Get total stock in library
balanceOf(address libraryPool, uint256 tokenId)
→ returns uint256┌──────────────────────────┐
│ Cover [Available] │ ← Green badge
├──────────────────────────┤
│ Book Title │
│ Author │
│ 🟢 Availability: 4/5 │
│ │
│ [Borrow] │ ← Black button
└──────────────────────────┘
┌──────────────────────────┐
│ Cover [Available] │ ← Green badge
├──────────────────────────┤
│ Book Title │
│ Author │
│ 🟢 Availability: 3/5 │
│ 🔵 Due: 2d 5h left │ ← NEW: Expiry countdown
│ │
│ [Read Book] [Return] │ ← Two buttons
└──────────────────────────┘
┌──────────────────────────┐
│ Cover [Not Available] │ ← Red badge
├──────────────────────────┤
│ Book Title │
│ Author │
│ 🔴 Availability: 0/5 │
│ │
│ [Not available] │ ← Disabled button
└──────────────────────────┘
┌──────────────────────────┐
│ Cover [Available] │
├──────────────────────────┤
│ Book Title │
│ Author │
│ 🟢 Availability: 3/5 │
│ 🔵 Due: 45m left │ ← Urgent countdown
│ │
│ [Read Book] [Return] │
└──────────────────────────┘
- Black background (
bg-zinc-900) - White text
- Icon: Book reader
- Action: Calls
borrowFromPool() - Shows "Borrowing..." during transaction
- Read Book: Black, full width left
- Opens EPUB reader at
/read-book/{bookId}
- Opens EPUB reader at
- Return: White with black border, compact right
- Calls
returnMyBorrow() - Shows "Returning..." during transaction
- Calls
- Gray background (disabled state)
- Gray text
- Icon: Slash
- Non-clickable
Shown above buttons with color coding:
Loading/Processing:
- Blue background (
bg-blue-50) - "Borrowing book..." / "Returning book..."
Success:
- Green background (
bg-green-50) - "Book borrowed successfully!" / "Book returned successfully!"
- Auto-clears after 2 seconds
Error:
- Red background (
bg-red-50) - "Error: [error message]"
function formatExpiryTime(expiryTimestamp: number) {
const timeLeft = expiryTimestamp - now;
if (timeLeft <= 0) return "Expired";
if (days > 0) return `${days}d ${hours}h left`;
if (hours > 0) return `${hours}h ${minutes}m left`;
return `${minutes}m left`;
}Examples:
172800 seconds→ "2d 0h left"12600 seconds→ "3h 30m left"1800 seconds→ "30m left"-100 seconds→ "Expired"
1. CivilibBookCard mounts
2. Fetches totalStock from main contract
3. Fetches frozenNow from library pool
4. Calculates availableBooks = totalStock - frozenNow
5. CivilibAccessButton calls getActiveBorrows(user)
6. Checks if user borrowed this book
7. If yes: displays expiry time
1. User clicks "Borrow" button
2. Set loading = true, show "Borrowing book..."
3. Call borrowFromPool(tokenId)
4. Wait for transaction confirmation
5. Wait 2 seconds for blockchain update
6. Call getActiveBorrows(user) to get recordId & expiry
7. Update UI: show "Read Book" + "Return" buttons
8. Display expiry countdown badge
9. Notify parent (CivilibBookCard) with expiry timestamp
1. User clicks "Return" button
2. Set loading = true, show "Returning book..."
3. Call returnMyBorrow(tokenId)
4. Wait for transaction confirmation
5. Wait 2 seconds
6. Update UI: show "Borrow" button again
7. Remove expiry badge
8. Notify parent with null expiry
Changes:
- Added "Not Available" red badge (complementing green "Available")
- Added
userBorrowExpirystate for tracking expiry - Added
formatExpiryTime()helper function - Added blue expiry badge display: "Due: Xd Xh left"
- Changed badge container to
flex-colfor vertical stacking - Pass
onBorrowStatusChangecallback to CivilibAccessButton
Major Refactor:
Removed:
- Old functions:
hasAccess(),accessRegistry(),rentAccess() - Main contract imports
Added:
- Library pool contract imports
getActiveBorrows()for checking borrow statusborrowFromPool()for borrowingreturnMyBorrow()for returningonBorrowStatusChangecallback prop- Status message display
- Loading states
- Two-button layout for borrowed state
New Props:
onBorrowStatusChange?: (expiry: number | null) => void
New States:
hasBorrowed: booleanborrowRecord: BorrowRecord | nullloading: booleanstatusMessage: string
- Exports library pool address
- Exports library pool ABI
Contract Addresses:
- Main Contract:
0xC12F333f41D7cedB209F24b303287531Bb05Bc67 - Library Pool:
0xA31D6d3f2a6C5fBA99E451CCAAaAdf0bca12cbF0
Network: Base Sepolia Testnet
- Green "Available" badge when books available
- Red "Not Available" badge when all borrowed
- No badge when book not in collection
- Correct availability count (X/Y format)
- "Borrow" button visible when available
- Status message shows during transaction
- Button disabled during loading
- Transaction calls
borrowFromPool() - UI updates after successful borrow
- Expiry time fetched and displayed
- Blue badge shows countdown
- Format: "Xd Xh left" or "Xh Xm left"
- Updates when borrow status changes
- Hidden when no active borrow
- "Return" button visible when borrowed
- Status message shows during transaction
- Transaction calls
returnMyBorrow() - UI updates after successful return
- Expiry badge removed
- "Borrow" button reappears
- Shows error message on failed borrow
- Shows error message on failed return
- Graceful handling of contract errors
- Console logs for debugging
- No wallet connected
- Book not in collection (totalStock = 0)
- All books borrowed (availableBooks = 0)
- Multiple books by same user
-
RecordId Tracking:
borrowRecordstate is set but not currently used. Could be used for advanced features like viewing borrow history. -
Real-time Updates: Expiry countdown is set once and doesn't tick down in real-time. Could add
setIntervalfor live countdown. -
Auto-refresh: After expiry, user needs to refresh page to see updated status. Could add periodic polling.
- Live Countdown: Update expiry every minute
- Borrow History: Show all past borrows
- Renewal: Extend borrow period
- Notifications: Alert when book due soon
- Waitlist: Queue when all books borrowed
- EAS Integration: On-chain attestations for borrows
✅ Full borrowing flow implemented
✅ Expiry tracking working
✅ UI responsive to borrow state
✅ Status messages clear
✅ Error handling robust
✅ Build succeeds without blocking errors
Ready for testing on Base Sepolia testnet! 🚀