bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
73 lines (72 loc) • 7.13 kB
JavaScript
"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" }) })] })] })] }) })] }));
}