Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/components/common/CreatorBio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ interface CreatorBioProps {
fallback?: string;
/** Variant — `card` is muted/italic for list rows, `profile` is slightly more prominent for the detail header. */
variant?: 'card' | 'profile';
/** If true, returns null instead of a fallback when bio is missing. */
allowEmpty?: boolean;
className?: string;
}

Expand All @@ -33,12 +35,17 @@ const CreatorBio: React.FC<CreatorBioProps> = ({
bio,
fallback = DEFAULT_FALLBACK,
variant = 'card',
allowEmpty = false,
className,
}) => {
const trimmed = bio?.trim();
const styles = variantClasses[variant];

if (!trimmed) {
if (allowEmpty) {
return null;
}

return (
<p className={cn(styles.fallback, className)} aria-label="Bio not provided">
{fallback}
Expand Down
13 changes: 11 additions & 2 deletions src/components/common/CreatorProfileStatItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,27 @@ const CreatorProfileStatItem: React.FC<CreatorProfileStatItemProps> = ({
helperText,
className,
}) => {
const accessibleLabel = typeof value === 'string' ? `${label}: ${value}` : undefined;

return (
<div
className={cn(
'group relative overflow-hidden rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-4 backdrop-blur-md transition-all duration-300 hover:border-amber-500/30 hover:bg-white/[0.06] hover:shadow-[0_8px_30px_rgb(0,0,0,0.12)]',
className
)}
aria-label={accessibleLabel}
>
<div className="absolute inset-0 bg-gradient-to-br from-white/[0.02] to-transparent opacity-0 transition-opacity duration-500 group-hover:opacity-100" />
<p className="relative z-10 text-[0.65rem] font-bold uppercase tracking-[0.22em] text-white/40 transition-colors duration-300 group-hover:text-amber-200/50">
<p
className="relative z-10 text-[0.65rem] font-bold uppercase tracking-[0.22em] text-white/40 transition-colors duration-300 group-hover:text-amber-200/50"
aria-hidden={accessibleLabel ? 'true' : 'false'}
>
{label}
</p>
<div className="relative z-10 mt-2.5 font-jakarta text-base font-bold text-white md:text-[1.05rem]">
<div
className="relative z-10 mt-2.5 font-jakarta text-base font-bold text-white md:text-[1.05rem]"
aria-hidden={accessibleLabel ? 'true' : 'false'}
>
{value}
</div>
{helperText && (
Expand Down
7 changes: 7 additions & 0 deletions src/components/common/CreatorSocialLinksList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ interface CreatorSocialLinksListProps {
handle?: string;
/** Override the default empty-state placeholder copy. */
emptyPlaceholder?: string;
/** If true, returns null instead of a placeholder when handle is missing. */
allowEmpty?: boolean;
className?: string;
}

Expand All @@ -27,9 +29,14 @@ interface SocialLinkItem {
const CreatorSocialLinksList: React.FC<CreatorSocialLinksListProps> = ({
handle,
emptyPlaceholder = DEFAULT_EMPTY_PLACEHOLDER,
allowEmpty = false,
className,
}) => {
if (!handle?.trim()) {
if (allowEmpty) {
return null;
}

return (
<div
role="note"
Expand Down
17 changes: 14 additions & 3 deletions src/components/common/EmptyTransactionTimelineState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface TimelineEntry {
status: 'confirmed' | 'pending' | 'failed';
}

const TIMELINE_ENTRIES: TimelineEntry[] = [
const DEFAULT_TIMELINE_ENTRIES: TimelineEntry[] = [
{
id: 'entry-1',
action: 'Buy',
Expand Down Expand Up @@ -43,9 +43,20 @@ const TIMELINE_ENTRIES: TimelineEntry[] = [

const shortenTxHash = (hash: string) => `${hash.slice(0, 8)}...${hash.slice(-6)}`;

const EmptyTransactionTimelineState: React.FC = () => {
interface EmptyTransactionTimelineStateProps {
/** Optional transaction data. If provided and empty, the component returns null. */
data?: TimelineEntry[];
}

const EmptyTransactionTimelineState: React.FC<EmptyTransactionTimelineStateProps> = ({
data = DEFAULT_TIMELINE_ENTRIES,
}) => {
const [copyStateById, setCopyStateById] = useState<Record<string, CopyState>>({});

if (!data || data.length === 0) {
return null;
}

const copyTxHash = async (entryId: string, txHash: string) => {
try {
await navigator.clipboard.writeText(txHash);
Expand Down Expand Up @@ -77,7 +88,7 @@ const EmptyTransactionTimelineState: React.FC = () => {
</div>

<div className="space-y-2">
{TIMELINE_ENTRIES.map(entry => {
{data.map(entry => {
const copyState = copyStateById[entry.id] ?? 'idle';
const isSuccess = copyState === 'success';
const isError = copyState === 'error';
Expand Down
7 changes: 7 additions & 0 deletions src/components/common/MarketplaceSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,23 @@ interface MarketplaceSectionProps
React.HTMLAttributes<HTMLElement>,
VariantProps<typeof marketplaceSectionVariants> {
as?: 'section' | 'div' | 'header' | 'footer';
/** If true, the section and its spacing/dividers will not be rendered. */
isEmpty?: boolean;
}

const MarketplaceSection: React.FC<MarketplaceSectionProps> = ({
children,
spacing,
container,
className,
isEmpty = false,
as: Tag = 'section',
...props
}) => {
if (isEmpty) {
return null;
}

return (
<Tag
className={cn(
Expand Down
7 changes: 7 additions & 0 deletions src/components/common/SectionDivider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ interface SectionDividerProps {
title?: string;
spacing?: SectionDividerSpacing;
className?: string;
/** If true, the divider will not be rendered. */
isEmpty?: boolean;
}

const spacingClasses: Record<SectionDividerSpacing, string> = {
Expand All @@ -18,7 +20,12 @@ function SectionDivider({
title,
spacing = 'default',
className,
isEmpty = false,
}: SectionDividerProps) {
if (isEmpty) {
return null;
}

return (
<div
className={cn(
Expand Down
4 changes: 3 additions & 1 deletion src/components/common/TradeDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ const TradeDialog: React.FC<TradeDialogProps> = ({
data-testid="trade-dialog-amount"
/>
<div className="flex flex-wrap items-center gap-2 text-xs text-white/45">
<span>Holdings: {formatNumber(availableHoldings)} keys</span>
<span aria-label={`Current wallet holdings: ${formatNumber(availableHoldings)} keys`}>
Holdings: {formatNumber(availableHoldings)} keys
</span>
{side === 'sell' &&
availableHoldings > 0 &&
Number.isFinite(parsedAmount) &&
Expand Down
6 changes: 5 additions & 1 deletion src/pages/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,10 @@ function LandingPage() {
<div className="text-xs font-bold uppercase tracking-[0.22em] text-white/40">
Your holdings
</div>
<div className="truncate font-jakarta text-sm font-bold text-white/85">
<div
className="truncate font-jakarta text-sm font-bold text-white/85"
aria-label={`Wallet holdings: ${formatNumber(featuredHoldings)} keys`}
>
{formatNumber(featuredHoldings)} keys
</div>
</div>
Expand Down Expand Up @@ -700,6 +703,7 @@ function LandingPage() {
<SectionDivider
title="Transaction timeline pattern"
spacing="relaxed"
isEmpty={false}
/>
<MarketplaceSection spacing="relaxed">
<EmptyTransactionTimelineState />
Expand Down
Loading