bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
104 lines (103 loc) • 11.6 kB
JavaScript
"use client";
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { CheckIcon, CopyIcon, DotsHorizontalIcon, EnvelopeClosedIcon, ExternalLinkIcon, GlobeIcon, HomeIcon, TokensIcon, } from "@radix-ui/react-icons";
import React from "react";
import { HEIGHTS } from "../../lib/layout-constants.js";
import { BitcoinImage } from "../ui-components/index.js";
import { Badge } from "../ui/badge.js";
import { Button } from "../ui/button.js";
import { Card } from "../ui/card.js";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "../ui/dropdown-menu.js";
import { ScrollArea } from "../ui/scroll-area.js";
import { Separator } from "../ui/separator.js";
/**
* ProfileCard supporting full schema.org Person/Organization structure
*
* Features:
* - Complete schema.org field support (name, email, url, location, etc.)
* - Person vs Organization type detection
* - Rich contact information display
* - Location and address handling
* - Bitcoin-specific fields (paymail, address)
* - Beautiful avatar with BitcoinImage wrapper
* - Scrollable bio section
* - Responsive design with multiple variants
*/
export function ProfileCard({ profile, showActions = true, onEdit, onSwitch, onViewDetails, menuItems, status, compact = false, variant = "surface", size = "2", showFullDetails = false, className = "", style, noWrapper = false, }) {
const [copied, setCopied] = React.useState(null);
const handleCopy = async (text, type) => {
try {
await navigator.clipboard.writeText(text);
setCopied(type);
setTimeout(() => setCopied(null), 2000);
}
catch (error) {
console.error("Failed to copy:", error);
}
};
const getInitials = (name) => {
if (!name)
return "?";
return name
.split(" ")
.map((word) => word[0])
.join("")
.toUpperCase()
.slice(0, 2);
};
const getDisplayName = () => {
// Support schema.org structure: alternateName, givenName+familyName, or fallback
if (profile.alternateName)
return profile.alternateName;
if (profile.givenName && profile.familyName) {
return `${profile.givenName} ${profile.familyName}`;
}
if (profile.givenName)
return profile.givenName;
if (profile.name)
return profile.name;
if (profile.legalName)
return profile.legalName;
return "Unnamed Profile";
};
const getProfileType = () => {
// Detect if it's a Person or Organization based on schema.org fields
if (profile.legalName || profile.logo)
return "Organization";
if (profile.givenName || profile.familyName || profile.homeLocation)
return "Person";
return "Person"; // Default
};
const profileType = getProfileType();
const displayName = getDisplayName();
const avatarImage = profileType === "Organization"
? profile.logo || profile.image
: profile.image;
// Use custom status or default to published/draft
const statusBadge = status || {
label: profile.isPublished ? "Published" : "Draft",
color: profile.isPublished ? "green" : "orange",
variant: "soft",
};
// Compact view for tight spaces
if (compact) {
return (_jsx(Card, { className: `p-3 ${className}`, style: style, children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(BitcoinImage, { src: avatarImage, alt: displayName, fallback: getInitials(displayName), className: "w-8 h-8 rounded-md" }), _jsxs("div", { className: "flex flex-col gap-1 flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "text-sm font-bold truncate", children: displayName }), _jsx(Badge, { variant: statusBadge.variant === "soft" ? "secondary" : "default", className: "text-xs", children: statusBadge.label })] }), profile.description && (_jsx("p", { className: "text-xs text-muted-foreground truncate", children: profile.description }))] }), onSwitch && (_jsx(Button, { size: "sm", variant: "ghost", onClick: onSwitch, children: _jsx(CheckIcon, { className: "h-4 w-4" }) }))] }) }));
}
// Full profile card view
const content = (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-start justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-4 flex-1 min-w-0", children: [_jsx(BitcoinImage, { src: avatarImage, alt: displayName, fallback: getInitials(displayName), className: "w-16 h-16 rounded-md" }), _jsxs("div", { className: "flex flex-col gap-2 flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-3 w-full", children: [_jsx("h3", { className: "text-lg font-bold flex-1 min-w-0", children: displayName }), _jsx(Badge, { variant: statusBadge.variant === "soft" ? "secondary" : "default", className: "text-xs", children: statusBadge.label })] }), profileType === "Organization" &&
profile.legalName &&
profile.legalName !== displayName && (_jsx("p", { className: "text-sm text-muted-foreground", children: profile.legalName })), profileType === "Person" &&
profile.givenName &&
profile.familyName &&
!profile.alternateName && (_jsxs("p", { className: "text-sm text-muted-foreground", children: [profile.givenName, " ", profile.familyName] })), _jsxs("div", { className: "flex items-center gap-2 w-full", children: [_jsxs("p", { className: "text-xs text-muted-foreground font-mono flex-1 min-w-0", children: [profile.address.slice(0, 8), "...", profile.address.slice(-6)] }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => handleCopy(profile.address, "address"), "aria-label": "Copy address", children: copied === "address" ? (_jsx(CheckIcon, { className: "h-3 w-3" })) : (_jsx(CopyIcon, { className: "h-3 w-3" })) }), _jsx(Button, { size: "sm", variant: "ghost", asChild: true, "aria-label": "View on blockchain", children: _jsx("a", { href: `https://whatsonchain.com/address/${profile.address}`, target: "_blank", rel: "noopener noreferrer", children: _jsx(ExternalLinkIcon, { className: "h-3 w-3" }) }) })] })] })] }), menuItems && menuItems.length > 0 && (_jsx("div", { className: "flex-shrink-0 ml-3", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { size: "sm", variant: "ghost", "aria-label": "More options", children: _jsx(DotsHorizontalIcon, { className: "h-4 w-4" }) }) }), _jsx(DropdownMenuContent, { align: "end", children: menuItems.map((item, index) => (_jsxs(DropdownMenuItem, { onClick: item.onClick, children: [item.icon && _jsx("span", { className: "mr-2", children: item.icon }), item.label] }, `profile-card-menu-item-${index + 1}`))) })] }) }))] }), profile.description && (_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium mb-2", children: "About" }), _jsx(ScrollArea, { className: "", style: {
height: showFullDetails ? "auto" : "60px",
maxHeight: showFullDetails ? HEIGHTS.CARD_MIN : "60px",
}, children: _jsx("p", { className: "text-sm leading-6", children: profile.description }) })] })), showFullDetails && (_jsxs("div", { className: "flex flex-col gap-3 w-full", children: [profile.email && (_jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-2 w-20 flex-shrink-0", children: [_jsx(EnvelopeClosedIcon, { className: "h-4 w-4" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Email" })] }), _jsx("div", { className: "flex-1 text-right pr-2", children: _jsx("a", { href: `mailto:${profile.email}`, className: "text-sm text-blue-600 hover:underline", children: profile.email }) }), _jsx("div", { className: "w-10 flex justify-end", children: _jsx(Button, { size: "sm", variant: "ghost", onClick: () => handleCopy(profile.email || "", "email"), children: copied === "email" ? (_jsx(CheckIcon, { className: "h-3 w-3" })) : (_jsx(CopyIcon, { className: "h-3 w-3" })) }) })] })), profile.url && (_jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-2 w-20 flex-shrink-0", children: [_jsx(GlobeIcon, { className: "h-4 w-4" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Website" })] }), _jsx("div", { className: "flex-1 text-right pr-2", children: _jsx("p", { className: "text-sm", children: profile.url.replace(/^https?:\/\//, "") }) }), _jsx("div", { className: "w-10 flex justify-end", children: _jsx(Button, { size: "sm", variant: "ghost", asChild: true, children: _jsx("a", { href: profile.url, target: "_blank", rel: "noopener noreferrer", children: _jsx(ExternalLinkIcon, { className: "h-3 w-3" }) }) }) })] })), (profile.location || profile.homeLocation) && (_jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-2 w-20 flex-shrink-0", children: [profileType === "Organization" ? (_jsx(TokensIcon, { className: "h-4 w-4" })) : (_jsx(HomeIcon, { className: "h-4 w-4" })), _jsx("p", { className: "text-sm text-muted-foreground", children: "Location" })] }), _jsx("div", { className: "flex-1 text-right pr-2", children: _jsx("p", { className: "text-sm", children: profileType === "Organization"
? profile.location
: profile.homeLocation }) }), _jsx("div", { className: "w-10" })] })), profile.paymail && (_jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-2 w-20 flex-shrink-0", children: [_jsx("span", { className: "text-sm", children: "\u20BF" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Paymail" })] }), _jsx("div", { className: "flex-1 text-right pr-2", children: _jsx("p", { className: "text-sm font-mono", children: profile.paymail }) }), _jsx("div", { className: "w-10 flex justify-end", children: _jsx(Button, { size: "sm", variant: "ghost", onClick: () => handleCopy(profile.paymail || "", "paymail"), children: copied === "paymail" ? (_jsx(CheckIcon, { className: "h-3 w-3" })) : (_jsx(CopyIcon, { className: "h-3 w-3" })) }) })] }))] })), showActions && (onEdit || onSwitch || onViewDetails) && (_jsxs(_Fragment, { children: [_jsx(Separator, { className: "my-4" }), _jsxs("div", { className: "flex flex-col sm:flex-row gap-2 sm:gap-3 justify-center sm:justify-end", children: [onViewDetails && (_jsx(Button, { size: "sm", variant: "outline", onClick: onViewDetails, children: "Details" })), onEdit && (_jsx(Button, { size: "sm", variant: "outline", onClick: onEdit, children: "Edit" })), onSwitch && (_jsxs(Button, { size: "sm", variant: "default", onClick: onSwitch, children: [_jsx(CheckIcon, { className: "h-4 w-4 mr-1" }), _jsx("span", { className: "hidden sm:inline", children: "Switch to Profile" }), _jsx("span", { className: "inline sm:hidden", children: "Switch" })] }))] })] }))] }));
// Conditionally wrap in Card
if (noWrapper) {
return content;
}
return (_jsx(Card, { className: `p-6 ${className}`, style: style, children: content }));
}