From 21ec5b4b381f952f13cc25b96f1c00faa07e3d0b Mon Sep 17 00:00:00 2001 From: Alaka-ibr Date: Thu, 27 Aug 2026 07:09:37 +0100 Subject: [PATCH 1/5] feat(dashboard): add co-creator revenue split display and configuration form --- src/components/creator/CoCreatorSection.tsx | 172 +++++++++++++++++ src/components/creator/SetCoCreatorModal.tsx | 173 ++++++++++++++++++ .../__tests__/CoCreatorSection.test.tsx | 134 ++++++++++++++ src/hooks/useCreators.ts | 31 +++- src/pages/CreatorDetailPage.tsx | 11 +- src/services/course.service.ts | 25 +++ src/utils/coCreator.utils.ts | 9 + 7 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 src/components/creator/CoCreatorSection.tsx create mode 100644 src/components/creator/SetCoCreatorModal.tsx create mode 100644 src/components/creator/__tests__/CoCreatorSection.test.tsx create mode 100644 src/utils/coCreator.utils.ts diff --git a/src/components/creator/CoCreatorSection.tsx b/src/components/creator/CoCreatorSection.tsx new file mode 100644 index 00000000..6901bc0a --- /dev/null +++ b/src/components/creator/CoCreatorSection.tsx @@ -0,0 +1,172 @@ +import { useState } from 'react'; +import { Copy, Check, UserCheck, Wallet } from 'lucide-react'; +import { truncateTxHash } from '@/constants/stellar'; +import { copyTextToClipboard } from '@/utils/clipboard.utils'; +import { bpsToPercent } from '@/utils/numberFormat.utils'; +import { formatDisplayKeyPrice } from '@/utils/keyPriceDisplay.utils'; +import SetCoCreatorModal from './SetCoCreatorModal'; + +interface CoCreatorSectionProps { + courseId: string; + coCreatorAddress?: string; + coCreatorSplitBps?: number; + totalPaidToCoCreator?: number; + totalPaidToCreator?: number; + className?: string; +} + +export function CoCreatorSection({ + courseId, + coCreatorAddress, + coCreatorSplitBps, + totalPaidToCoCreator = 0, + totalPaidToCreator = 0, + className = '', +}: CoCreatorSectionProps) { + const [isModalOpen, setIsModalOpen] = useState(false); + const [copied, setCopied] = useState(false); + + const hasCoCreator = Boolean(coCreatorAddress && coCreatorSplitBps && coCreatorSplitBps > 0); + + const handleCopyAddress = async () => { + if (!coCreatorAddress) return; + try { + await copyTextToClipboard(coCreatorAddress); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch { + // Handle copy error silently + } + }; + + return ( +
+
+
+
+
+
+

+ Co-Creator Royalty Split +

+

+ Revenue split arrangement for secondary royalties and key sales +

+
+
+ + +
+ + {!hasCoCreator ? ( +
+
+ ) : ( +
+ {/* Co-creator Config Info */} +
+
+ + Co-Creator Address + +
+ + {truncateTxHash(coCreatorAddress ?? '', 8, 8)} + + +
+
+ +
+ + Split Percentage + +
+ + {bpsToPercent(coCreatorSplitBps ?? 0)} + +
+
+
+ + {/* Stat Cards */} +
+
+ + Total Paid to Co-Creator + +

+ {formatDisplayKeyPrice(totalPaidToCoCreator)} +

+
+ +
+ + Total Paid to Creator + +

+ {formatDisplayKeyPrice(totalPaidToCreator)} +

+
+
+
+ )} + + +
+ ); +} + +export default CoCreatorSection; diff --git a/src/components/creator/SetCoCreatorModal.tsx b/src/components/creator/SetCoCreatorModal.tsx new file mode 100644 index 00000000..7298d169 --- /dev/null +++ b/src/components/creator/SetCoCreatorModal.tsx @@ -0,0 +1,173 @@ +import React, { useState } from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { useSetCoCreator } from '@/hooks/useCreators'; + +interface SetCoCreatorModalProps { + courseId: string; + open: boolean; + onOpenChange: (open: boolean) => void; + initialAddress?: string; + initialSplitBps?: number; +} + + + +export function SetCoCreatorModal({ + courseId, + open, + onOpenChange, + initialAddress = '', + initialSplitBps, +}: SetCoCreatorModalProps) { + const [address, setAddress] = useState(initialAddress); + const [splitBps, setSplitBps] = useState( + initialSplitBps ? String(initialSplitBps) : '' + ); + const [addressError, setAddressError] = useState(''); + const [bpsError, setBpsError] = useState(''); + + const setCoCreatorMutation = useSetCoCreator(courseId); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + let hasError = false; + setAddressError(''); + setBpsError(''); + + const trimmedAddress = address.trim(); + if (!trimmedAddress) { + setAddressError('Co-creator Stellar address is required'); + hasError = true; + } else if (!isValidStellarAddress(trimmedAddress)) { + setAddressError( + 'Invalid Stellar address. Must start with G and be 56 characters long.' + ); + hasError = true; + } + + const parsedBps = parseInt(splitBps, 10); + if (!splitBps || Number.isNaN(parsedBps)) { + setBpsError('Split percentage (bps) is required'); + hasError = true; + } else if (!isValidBps(parsedBps)) { + setBpsError('Split basis points must be an integer between 1 and 10000 (0.01% to 100%)'); + hasError = true; + } + + if (hasError) return; + + try { + await setCoCreatorMutation.mutateAsync({ + address: trimmedAddress, + splitBps: parsedBps, + }); + onOpenChange(false); + } catch { + // Toast notification handled in useSetCoCreator mutation + } + }; + + return ( + + + + + Configure Co-Creator Split + + + Set the Stellar wallet address and royalty split percentage (in basis points, e.g., 2500 = 25%) for your co-creator. + + + +
+
+ + setAddress(e.target.value)} + placeholder="G..." + className="w-full rounded-xl bg-white/[0.05] border border-white/10 px-3.5 py-2.5 text-sm text-white placeholder:text-white/30 focus:border-amber-400 focus:outline-none" + /> + {addressError && ( +

+ {addressError} +

+ )} +
+ +
+ + setSplitBps(e.target.value)} + placeholder="e.g. 2500 for 25%" + min="1" + max="10000" + className="w-full rounded-xl bg-white/[0.05] border border-white/10 px-3.5 py-2.5 text-sm text-white placeholder:text-white/30 focus:border-amber-400 focus:outline-none" + /> + {bpsError && ( +

+ {bpsError} +

+ )} +
+ + + + + +
+
+
+ ); +} + +export default SetCoCreatorModal; diff --git a/src/components/creator/__tests__/CoCreatorSection.test.tsx b/src/components/creator/__tests__/CoCreatorSection.test.tsx new file mode 100644 index 00000000..f9ffa048 --- /dev/null +++ b/src/components/creator/__tests__/CoCreatorSection.test.tsx @@ -0,0 +1,134 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import CoCreatorSection from '../CoCreatorSection'; +import { isValidStellarAddress, isValidBps } from '@/utils/coCreator.utils'; + +vi.mock('@/utils/toast.util', () => ({ + default: { + success: vi.fn(), + error: vi.fn(), + }, +})); + +vi.mock('@/services/course.service', () => ({ + courseService: { + setCoCreator: vi.fn().mockResolvedValue({ + id: 'course_123', + coCreatorAddress: 'GA7QW3L7Y54N4P5O3G6J8K9L0M1N2P3Q4R5S6T7U8V9W0X1Y2Z3A4B5C', + coCreatorSplitBps: 2500, + }), + }, +})); + +function makeWrapper() { + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, + }); + return ({ children }: { children: React.ReactNode }) => ( + {children} + ); +} + +describe('Stellar Address and BPS validation helpers', () => { + it('validates Stellar G-addresses correctly', () => { + const validAddress = 'GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFXYSFIZGK63PZZVVJAB7'; + expect(isValidStellarAddress(validAddress)).toBe(true); + + expect(isValidStellarAddress('invalid_address')).toBe(false); + expect(isValidStellarAddress('0x1234567890abcdef')).toBe(false); + expect(isValidStellarAddress('G123')).toBe(false); + }); + + it('validates basis points range correctly', () => { + expect(isValidBps(2500)).toBe(true); + expect(isValidBps(1)).toBe(true); + expect(isValidBps(10000)).toBe(true); + + expect(isValidBps(0)).toBe(false); + expect(isValidBps(10001)).toBe(false); + expect(isValidBps(-500)).toBe(false); + expect(isValidBps(25.5)).toBe(false); + }); +}); + +describe('CoCreatorSection Component', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('renders "No co-creator configured" empty state when no split is configured', () => { + render(, { + wrapper: makeWrapper(), + }); + + expect(screen.getByTestId('cocreator-empty-state')).toBeInTheDocument(); + expect(screen.getByText('No co-creator configured')).toBeInTheDocument(); + expect(screen.getByTestId('set-cocreator-button')).toHaveTextContent( + 'Set Co-Creator' + ); + }); + + it('renders truncated address, split percentage, and stat cards when co-creator is set', () => { + const coCreatorAddress = + 'GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFXYSFIZGK63PZZVVJAB7'; + render( + , + { wrapper: makeWrapper() } + ); + + expect(screen.queryByTestId('cocreator-empty-state')).not.toBeInTheDocument(); + expect(screen.getByTestId('cocreator-details')).toBeInTheDocument(); + + expect(screen.getByTestId('cocreator-split-display')).toHaveTextContent('25%'); + expect(screen.getByTestId('total-paid-cocreator')).toHaveTextContent('15 XLM'); + expect(screen.getByTestId('total-paid-creator')).toHaveTextContent('45 XLM'); + expect(screen.getByTestId('set-cocreator-button')).toHaveTextContent( + 'Edit Co-Creator' + ); + }); + + it('opens the configuration modal when Set Co-Creator is clicked', async () => { + const user = userEvent.setup(); + render(, { + wrapper: makeWrapper(), + }); + + await user.click(screen.getByTestId('set-cocreator-button')); + + expect(screen.getByTestId('set-cocreator-modal')).toBeInTheDocument(); + expect( + screen.getByRole('heading', { name: /configure co-creator split/i }) + ).toBeInTheDocument(); + }); + + it('shows error messages when submitting invalid Stellar address or BPS', async () => { + const user = userEvent.setup(); + render(, { + wrapper: makeWrapper(), + }); + + await user.click(screen.getByTestId('set-cocreator-button')); + + const addressInput = screen.getByTestId('cocreator-address-input'); + const bpsInput = screen.getByTestId('cocreator-bps-input'); + const submitBtn = screen.getByTestId('submit-cocreator-button'); + + // Type invalid address and invalid BPS + await user.type(addressInput, 'invalid-address'); + await user.type(bpsInput, '20000'); + await user.click(submitBtn); + + await waitFor(() => { + expect(screen.getByTestId('cocreator-address-error')).toBeInTheDocument(); + expect(screen.getByTestId('cocreator-bps-error')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/hooks/useCreators.ts b/src/hooks/useCreators.ts index 32fd514d..98343e50 100644 --- a/src/hooks/useCreators.ts +++ b/src/hooks/useCreators.ts @@ -1,9 +1,11 @@ -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { queryKeys } from '@/lib/queryKeys'; import { courseService, + type Course, type GetCoursesParams, } from '@/services/course.service'; +import showToast from '@/utils/toast.util'; export function useCreatorList(params?: GetCoursesParams) { return useQuery({ @@ -20,3 +22,30 @@ export function useCreatorDetail(id: string) { }); } +export function useSetCoCreator(courseId: string) { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ address, splitBps }: { address: string; splitBps: number }) => + courseService.setCoCreator(courseId, address, splitBps), + onSuccess: (updatedCourse: Course) => { + if (updatedCourse) { + queryClient.setQueryData( + queryKeys.creators.detail(courseId), + updatedCourse + ); + } + void queryClient.invalidateQueries({ + queryKey: queryKeys.creators.detail(courseId), + }); + showToast.success('Co-creator configured successfully'); + }, + onError: (error: unknown) => { + const message = + error instanceof Error ? error.message : 'Failed to set co-creator'; + showToast.error(message); + }, + }); +} + + diff --git a/src/pages/CreatorDetailPage.tsx b/src/pages/CreatorDetailPage.tsx index e835c9e9..fa72198b 100644 --- a/src/pages/CreatorDetailPage.tsx +++ b/src/pages/CreatorDetailPage.tsx @@ -16,7 +16,7 @@ import KeyDetailPageErrorBoundary from '@/components/common/KeyDetailPageErrorBo import { ApiError } from '@/services/api.service'; import { useNavigationTiming } from '@/hooks/useNavigationTiming'; import { useKeyHolders } from '@/hooks/useKeyHolders'; -import KeyHolderList from '@/components/common/KeyHolderList'; +import CoCreatorSection from '@/components/creator/CoCreatorSection'; function CreatorDetailPageContent() { const { id } = useParams<{ id: string }>(); @@ -174,6 +174,15 @@ function CreatorDetailPageContent() { + {/* Co-Creator Section */} + + {/* Activity Feed */}

diff --git a/src/services/course.service.ts b/src/services/course.service.ts index 8e605ed8..88bd5945 100644 --- a/src/services/course.service.ts +++ b/src/services/course.service.ts @@ -27,6 +27,13 @@ export interface Course { protocolFeeBps?: number; /** Last up to 7 price history points in stroops, oldest to newest. */ priceHistory?: number[]; + holderCount?: number; + holdersCount?: number; + holders?: number; + coCreatorAddress?: string; + coCreatorSplitBps?: number; + totalPaidToCoCreator?: number; + totalPaidToCreator?: number; } export type CourseSortOption = @@ -257,6 +264,24 @@ class CourseService extends BaseApiService { throw this.handleError(error); } } + + // Set co-creator address and split — POST /courses/:id/co-creator + async setCoCreator( + courseId: string, + address: string, + splitBps: number + ): Promise { + try { + const response = await this.api.post>( + `/courses/${courseId}/co-creator`, + { address, splitBps } + ); + + return response.data.data; + } catch (error) { + throw this.handleError(error); + } + } } export const courseService = new CourseService(); diff --git a/src/utils/coCreator.utils.ts b/src/utils/coCreator.utils.ts new file mode 100644 index 00000000..8b3a590b --- /dev/null +++ b/src/utils/coCreator.utils.ts @@ -0,0 +1,9 @@ +/** Stellar G-address validation: starts with 'G' followed by 55 base32 characters */ +export function isValidStellarAddress(address: string): boolean { + return /^G[A-Z2-7]{55}$/.test(address.trim()); +} + +/** Basis points validation: integer between 1 and 10000 (0.01% to 100%) */ +export function isValidBps(bps: number): boolean { + return Number.isInteger(bps) && bps >= 1 && bps <= 10000; +} From 9a9de1dbbcb371f46b5286140d9459589f1f2863 Mon Sep 17 00:00:00 2001 From: Alaka-ibr Date: Thu, 27 Aug 2026 07:16:53 +0100 Subject: [PATCH 2/5] feat(notifications): add global notification drawer with 60s polling and event icons --- src/components/common/NotificationBell.tsx | 129 +++++++++++++----- .../__tests__/NotificationBell.test.tsx | 82 ++++++++++- src/components/creator/SetCoCreatorModal.tsx | 1 + src/hooks/__tests__/useNotifications.test.ts | 19 ++- src/hooks/useNotifications.ts | 58 ++++++-- src/services/notification.service.ts | 17 ++- 6 files changed, 250 insertions(+), 56 deletions(-) diff --git a/src/components/common/NotificationBell.tsx b/src/components/common/NotificationBell.tsx index 3cbefd94..37844f3d 100644 --- a/src/components/common/NotificationBell.tsx +++ b/src/components/common/NotificationBell.tsx @@ -1,8 +1,16 @@ -import { Bell } from 'lucide-react'; +import { + Bell, + ArrowRightLeft, + Clock, + TrendingUp, + UserPlus, + Key, +} from 'lucide-react'; import { useNavigate } from 'react-router'; import { cn } from '@/lib/utils'; import { useNotifications } from '@/hooks/useNotifications'; import { formatRelativeTime } from '@/utils/time.utils'; +import type { NotificationType } from '@/services/notification.service'; import { DropdownMenu, DropdownMenuContent, @@ -18,16 +26,80 @@ interface NotificationBellProps { className?: string; } +function renderNotificationIcon(type: NotificationType) { + switch (type) { + case 'trade_completed': + return ( +

+ {/* Share to X Button (only visible for authenticated holders) */} +
+ +
+ {/* Price Chart */}
Date: Sat, 29 Aug 2026 00:49:14 +0100 Subject: [PATCH 4/5] chore: resolve merge conflicts with dev branch - Merge staking fields (stakingPoolBalance, totalStaked, recentFeeInflow, name, bio, avatarUri, auctionPrice, auctionSupply, auctionSold) and stakedQuantity on KeyHolderEntry from upstream/dev into course.service.ts - Keep co-creator fields and setCoCreator() method from this branch - Merge StakingRewardsSection from upstream/dev into CreatorDetailPage - Keep ShareTwitterButton, CoCreatorSection, and improved 404 handling from this branch in CreatorDetailPage --- src/pages/CreatorDetailPage.tsx | 36 ++++++++++++++++++++++++++++----- src/services/course.service.ts | 23 +++++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/pages/CreatorDetailPage.tsx b/src/pages/CreatorDetailPage.tsx index fcc932b8..9a364346 100644 --- a/src/pages/CreatorDetailPage.tsx +++ b/src/pages/CreatorDetailPage.tsx @@ -9,6 +9,7 @@ import CreatorProfileStaleIndicator from '@/components/common/CreatorProfileStal import CreatorProfileStatRow from '@/components/common/CreatorProfileStatRow'; import { BondingCurveChart } from '@/components/common/BondingCurveChart'; import KeyHolderList from '@/components/common/KeyHolderList'; +import StakingRewardsSection from '@/components/common/StakingRewardsSection'; import { CreatorDashboardSkeleton } from '@/components/common/CreatorSkeleton'; import { bpsToPercent, formatNumber } from '@/utils/numberFormat.utils'; import { resolveCreatorKeyPriceStroops, formatDisplayKeyPrice } from '@/utils/keyPriceDisplay.utils'; @@ -121,13 +122,32 @@ function CreatorDetailPageContent() { })); const defaultHolders = [ - { id: 'h1', displayName: 'Early Adopter', keyCount: 25, sharePercent: 25 }, - { id: 'h2', displayName: 'Alpha Collector', keyCount: 15, sharePercent: 15 }, - { id: 'h3', displayName: 'Key Holder 3', keyCount: 10, sharePercent: 10 }, - { id: 'h4', displayName: 'Key Holder 4', keyCount: 8, sharePercent: 8 }, - { id: 'h5', displayName: 'Key Holder 5', keyCount: 5, sharePercent: 5 }, + { id: 'h1', displayName: 'Early Adopter', keyCount: 25, stakedQuantity: 18 }, + { id: 'h2', displayName: 'Alpha Collector', keyCount: 15, stakedQuantity: 5 }, + { id: 'h3', displayName: 'Key Holder 3', keyCount: 10, stakedQuantity: 0 }, + { id: 'h4', displayName: 'Key Holder 4', keyCount: 8, stakedQuantity: 0 }, + { id: 'h5', displayName: 'Key Holder 5', keyCount: 5, stakedQuantity: 2 }, ]; + const hasRealStakingData = + creator.stakingPoolBalance != null || + creator.totalStaked != null || + creator.recentFeeInflow != null; + const stakingStats = hasRealStakingData + ? { + stakingPoolBalance: creator.stakingPoolBalance, + totalStaked: creator.totalStaked, + recentFeeInflow: creator.recentFeeInflow, + } + : { + // Demo values until the key detail API returns staking pool stats. + stakingPoolBalance: 4820, + totalStaked: creator.creatorShareSupply + ? Math.floor(creator.creatorShareSupply / 4) + : 25, + recentFeeInflow: 62, + }; + return (
@@ -165,6 +185,12 @@ function CreatorDetailPageContent() { />
+ {/* Staking Rewards */} + + {/* Price Chart */}
Date: Sat, 29 Aug 2026 01:51:57 +0100 Subject: [PATCH 5/5] test: align test assertions with upstream formatting changes --- .github/workflows/pr-target-check.yml | 48 +++++++++---------- .../TradeDialog.sellPayoutDisplay.test.tsx | 6 +-- .../WalletConnectionPopover.test.tsx | 2 +- .../__tests__/CoCreatorSection.test.tsx | 4 +- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/pr-target-check.yml b/.github/workflows/pr-target-check.yml index 18af621f..5803a10b 100644 --- a/.github/workflows/pr-target-check.yml +++ b/.github/workflows/pr-target-check.yml @@ -1,29 +1,29 @@ name: Redirect PRs to dev on: - pull_request_target: - branches: - - main + pull_request_target: + branches: + - main jobs: - close-and-redirect: - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Close PR and redirect to dev - uses: actions/github-script@v7 - with: - script: | - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.pull_request.number, - body: "👋 Thanks for your contribution! We don't accept pull requests directly to `main`. Please re-open your PR targeting the `dev` branch instead. See our contributing guide for details." - }); - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.payload.pull_request.number, - state: 'closed' - }); + close-and-redirect: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Close PR and redirect to dev + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: "👋 Thanks for your contribution! We don't accept pull requests directly to `main`. Please re-open your PR targeting the `dev` branch instead. See our contributing guide for details." + }); + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + state: 'closed' + }); diff --git a/src/components/common/__tests__/TradeDialog.sellPayoutDisplay.test.tsx b/src/components/common/__tests__/TradeDialog.sellPayoutDisplay.test.tsx index 2229ab9f..4260c362 100644 --- a/src/components/common/__tests__/TradeDialog.sellPayoutDisplay.test.tsx +++ b/src/components/common/__tests__/TradeDialog.sellPayoutDisplay.test.tsx @@ -37,7 +37,7 @@ describe('TradeDialog – sell payout display (#692)', () => { fireEvent.change(input, { target: { value: '2' } }); expect(screen.getByText(/Estimated proceeds/i)).toBeInTheDocument(); - expect(screen.getByText('0.1 XLM')).toBeInTheDocument(); + expect(screen.getByText(/0\.10? XLM/)).toBeInTheDocument(); }); it('updates the displayed payout as the sell quantity changes', () => { @@ -47,10 +47,10 @@ describe('TradeDialog – sell payout display (#692)', () => { ) as HTMLInputElement; fireEvent.change(input, { target: { value: '1' } }); - expect(screen.getByText('0.05 XLM')).toBeInTheDocument(); + expect(screen.getByText(/0\.05 XLM/)).toBeInTheDocument(); fireEvent.change(input, { target: { value: '4' } }); - expect(screen.getByText('0.2 XLM')).toBeInTheDocument(); + expect(screen.getByText(/0\.20? XLM/)).toBeInTheDocument(); expect(screen.queryByText('0.05 XLM')).not.toBeInTheDocument(); }); diff --git a/src/components/common/__tests__/WalletConnectionPopover.test.tsx b/src/components/common/__tests__/WalletConnectionPopover.test.tsx index e30c5f25..acdbbf2b 100644 --- a/src/components/common/__tests__/WalletConnectionPopover.test.tsx +++ b/src/components/common/__tests__/WalletConnectionPopover.test.tsx @@ -184,7 +184,7 @@ describe('WalletConnectionPopover — disconnected state', () => { describe('WalletConnectionPopover — connected state', () => { const FULL_ADDRESS = '0xAbCdEf1234567890AbCdEf1234567890AbCdEf12'; // shortenAddress defaults: first 6 chars + '...' + last 4 chars - const TRUNCATED_ADDRESS = '0xAbCd...f12'.slice(0, 6) + '...' + FULL_ADDRESS.slice(-4); + const TRUNCATED_ADDRESS = `${FULL_ADDRESS.slice(0, 4)}...${FULL_ADDRESS.slice(-4)}`; beforeEach(() => { vi.clearAllMocks(); diff --git a/src/components/creator/__tests__/CoCreatorSection.test.tsx b/src/components/creator/__tests__/CoCreatorSection.test.tsx index f9ffa048..c41e0ece 100644 --- a/src/components/creator/__tests__/CoCreatorSection.test.tsx +++ b/src/components/creator/__tests__/CoCreatorSection.test.tsx @@ -88,8 +88,8 @@ describe('CoCreatorSection Component', () => { expect(screen.getByTestId('cocreator-details')).toBeInTheDocument(); expect(screen.getByTestId('cocreator-split-display')).toHaveTextContent('25%'); - expect(screen.getByTestId('total-paid-cocreator')).toHaveTextContent('15 XLM'); - expect(screen.getByTestId('total-paid-creator')).toHaveTextContent('45 XLM'); + expect(screen.getByTestId('total-paid-cocreator')).toHaveTextContent(/15.*XLM/); + expect(screen.getByTestId('total-paid-creator')).toHaveTextContent(/45.*XLM/); expect(screen.getByTestId('set-cocreator-button')).toHaveTextContent( 'Edit Co-Creator' );