UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

73 lines (72 loc) 7.13 kB
"use client"; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { CheckIcon, ImageIcon, PersonIcon } from "@radix-ui/react-icons"; import { useState } from "react"; import { CONTAINER_WIDTHS } from "../../lib/layout-constants.js"; import { cn } from "../../lib/utils.js"; import { LoadingButton } from "../ui-components/LoadingButton.js"; import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar.js"; import { Button } from "../ui/button.js"; import { Card, CardContent } from "../ui/card.js"; import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle, } from "../ui/dialog.js"; import { Input } from "../ui/input.js"; import { Textarea } from "../ui/textarea.js"; /** * ProfileEditor allows editing BAP profile information * * Features: * - Edit profile name, image URL, and description * - Avatar preview with fallback * - Character limits and validation * - Save/cancel actions * - Loading states * - Error handling */ export function ProfileEditor({ profile, onSave, onCancel, allowImageUpload: _allowImageUpload = false, maxNameLength = 50, maxDescriptionLength = 200, className = "", variant = "default", size = "default", }) { // Helper to convert size prop to valid input size const inputSize = size === "lg" ? "default" : size; const [name, setName] = useState(profile.name || ""); const [image, setImage] = useState(profile.image || ""); const [description, setDescription] = useState(profile.description || ""); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [showImageDialog, setShowImageDialog] = useState(false); const hasChanges = name !== (profile.name || "") || image !== (profile.image || "") || description !== (profile.description || ""); const isValid = name.length > 0 && name.length <= maxNameLength && description.length <= maxDescriptionLength; const handleSave = async () => { if (!isValid || !hasChanges) return; setIsLoading(true); setError(null); try { await onSave({ name: name.trim(), image: image.trim() || undefined, description: description.trim() || undefined, }); } catch (err) { setError(err instanceof Error ? err.message : "Failed to save profile"); setIsLoading(false); } }; const handleImageChange = (url) => { setImage(url); setShowImageDialog(false); }; const getInitials = (profileName) => { if (!profileName) return "?"; return profileName .split(" ") .map((word) => word[0]) .join("") .toUpperCase() .slice(0, 2); }; return (_jsxs(_Fragment, { children: [_jsx(Card, { className: cn(variant === "outline" && "border", variant === "ghost" && "border-0 shadow-none", className), style: { maxWidth: CONTAINER_WIDTHS.FORM_DEFAULT }, children: _jsx(CardContent, { className: "p-6", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx("h3", { className: "text-lg font-bold", children: "Edit Profile" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs(Avatar, { className: "h-16 w-16", children: [_jsx(AvatarImage, { src: image }), _jsx(AvatarFallback, { children: getInitials(name || profile.name) })] }), _jsxs("div", { className: "flex flex-col gap-1 flex-1", children: [_jsx("p", { className: "text-sm font-medium", children: "Profile Picture" }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "secondary", size: "sm", onClick: () => setShowImageDialog(true), children: [_jsx(ImageIcon, { className: "w-4 h-4 mr-1" }), "Change Image"] }), image && (_jsx(Button, { variant: "ghost", size: "sm", className: "text-destructive", onClick: () => setImage(""), children: "Remove" }))] })] })] }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between mb-1", children: [_jsx("p", { className: "text-sm font-medium", children: "Display Name" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [name.length, "/", maxNameLength] })] }), _jsxs("div", { className: "relative", children: [_jsx(PersonIcon, { className: "absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" }), _jsx(Input, { value: name, onChange: (e) => setName(e.target.value), placeholder: "Enter your name", maxLength: maxNameLength, className: cn("pl-10", inputSize === "sm" && "h-8", inputSize === "default" && "h-10") })] })] }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between mb-1", children: [_jsx("p", { className: "text-sm font-medium", children: "Bio" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [description.length, "/", maxDescriptionLength] })] }), _jsx(Textarea, { value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Tell us about yourself", maxLength: maxDescriptionLength, className: "min-h-[80px]" })] }), error && (_jsx(Card, { className: "bg-red-50 border-red-200", children: _jsx(CardContent, { className: "p-3", children: _jsx("p", { className: "text-sm text-red-600", children: error }) }) })), _jsxs("div", { className: "flex gap-3 justify-end", children: [onCancel && (_jsx(Button, { variant: "secondary", onClick: onCancel, disabled: isLoading, children: "Cancel" })), _jsxs(LoadingButton, { loading: isLoading, disabled: !hasChanges || !isValid, onClick: handleSave, children: [_jsx(CheckIcon, { className: "w-4 h-4 mr-1" }), "Save Changes"] })] })] }) }) }), _jsx(Dialog, { open: showImageDialog, onOpenChange: setShowImageDialog, children: _jsxs(DialogContent, { className: "max-w-[450px]", children: [_jsx(DialogTitle, { children: "Set Profile Image" }), _jsx(DialogDescription, { className: "mb-4", children: "Enter the URL of your profile image. Make sure it's a valid image URL." }), _jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "relative", children: [_jsx(ImageIcon, { className: "absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" }), _jsx(Input, { value: image, onChange: (e) => setImage(e.target.value), placeholder: "https://example.com/image.jpg", className: "pl-10" })] }), image && (_jsx(Card, { className: "bg-muted", children: _jsx(CardContent, { className: "p-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Avatar, { className: "h-12 w-12", children: [_jsx(AvatarImage, { src: image }), _jsx(AvatarFallback, { children: "?" })] }), _jsx("p", { className: "text-sm", children: "Preview" })] }) }) })), _jsxs("div", { className: "flex gap-3 mt-4 justify-end", children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "secondary", children: "Cancel" }) }), _jsx(DialogClose, { asChild: true, children: _jsx(Button, { onClick: () => handleImageChange(image), children: "Apply" }) })] })] })] }) })] })); }