{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "settings-profile",
  "title": "Settings Profile",
  "description": "Manage profile information, avatar, and social links.",
  "registryDependencies": [
    "button",
    "card",
    "field",
    "input-group",
    "separator",
    "textarea"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/settings/settings-profile.tsx",
      "content": "\"use client\";\n\nimport { Camera, Loader2, Save, X } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { useCallback, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport {\n  Field,\n  FieldContent,\n  FieldDescription,\n  FieldError,\n  FieldLabel,\n} from \"@/registry/new-york/ui/field\";\nimport {\n  InputGroup,\n  InputGroupInput,\n} from \"@/registry/new-york/ui/input-group\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\nimport { Textarea } from \"@/registry/new-york/ui/textarea\";\n\nexport interface SocialLink {\n  platform: string;\n  url: string;\n}\n\nexport interface ProfileData {\n  name: string;\n  email: string;\n  bio?: string;\n  location?: string;\n  website?: string;\n  avatar?: string;\n  socialLinks?: SocialLink[];\n}\n\nexport interface SettingsProfileProps {\n  profile?: ProfileData;\n  onSave?: (data: ProfileData) => Promise<void>;\n  onEmailChange?: (newEmail: string, currentPassword: string) => Promise<void>;\n  onAvatarUpload?: (file: File) => Promise<string>;\n  onAvatarRemove?: () => Promise<void>;\n  className?: string;\n  showEmailVerification?: boolean;\n}\n\nconst defaultSocialPlatforms = [\n  {\n    id: \"twitter\",\n    label: \"Twitter/X\",\n    placeholder: \"https://x.com/preetsuthar17\",\n  },\n  {\n    id: \"github\",\n    label: \"GitHub\",\n    placeholder: \"https://github.com/preetsuthar17\",\n  },\n  {\n    id: \"linkedin\",\n    label: \"LinkedIn\",\n    placeholder: \"https://linkedin.com/in/preetsuthar17\",\n  },\n  { id: \"website\", label: \"Website\", placeholder: \"https://preetsuthar.me\" },\n];\n\nexport default function SettingsProfile({\n  profile,\n  onSave,\n  onEmailChange,\n  onAvatarUpload,\n  onAvatarRemove,\n  className,\n  showEmailVerification = true,\n}: SettingsProfileProps) {\n  const [isSaving, setIsSaving] = useState(false);\n  const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);\n  const [isChangingEmail, setIsChangingEmail] = useState(false);\n  const [showEmailChangeForm, setShowEmailChangeForm] = useState(false);\n  const [avatarPreview, setAvatarPreview] = useState<string | null>(\n    profile?.avatar || null\n  );\n  const [errors, setErrors] = useState<Record<string, string>>({});\n\n  const [formData, setFormData] = useState<ProfileData>({\n    name: profile?.name || \"\",\n    email: profile?.email || \"\",\n    bio: profile?.bio || \"\",\n    location: profile?.location || \"\",\n    website: profile?.website || \"\",\n    socialLinks: profile?.socialLinks || [],\n  });\n\n  const [emailChangeData, setEmailChangeData] = useState({\n    newEmail: \"\",\n    currentPassword: \"\",\n  });\n\n  const fileInputRef = useRef<HTMLInputElement>(null);\n  const avatarFileRef = useRef<File | null>(null);\n\n  const handleAvatarSelect = useCallback(\n    async (file: File) => {\n      if (!file.type.startsWith(\"image/\")) {\n        setErrors({ avatar: \"Please select an image file\" });\n        return;\n      }\n\n      if (file.size > 5 * 1024 * 1024) {\n        setErrors({ avatar: \"Image size must be less than 5MB\" });\n        return;\n      }\n\n      avatarFileRef.current = file;\n      const reader = new FileReader();\n      reader.onloadend = () => {\n        setAvatarPreview(reader.result as string);\n      };\n      reader.readAsDataURL(file);\n\n      if (onAvatarUpload) {\n        setIsUploadingAvatar(true);\n        try {\n          const avatarUrl = await onAvatarUpload(file);\n          setAvatarPreview(avatarUrl);\n          setErrors({});\n        } catch (error) {\n          setErrors({\n            avatar:\n              error instanceof Error\n                ? error.message\n                : \"Failed to upload avatar\",\n          });\n        } finally {\n          setIsUploadingAvatar(false);\n        }\n      }\n    },\n    [onAvatarUpload]\n  );\n\n  const handleAvatarClick = () => {\n    fileInputRef.current?.click();\n  };\n\n  const handleAvatarRemove = async () => {\n    if (onAvatarRemove) {\n      setIsUploadingAvatar(true);\n      try {\n        await onAvatarRemove();\n        setAvatarPreview(null);\n        avatarFileRef.current = null;\n        setErrors({});\n      } catch (error) {\n        setErrors({\n          avatar:\n            error instanceof Error ? error.message : \"Failed to remove avatar\",\n        });\n      } finally {\n        setIsUploadingAvatar(false);\n      }\n    } else {\n      setAvatarPreview(null);\n      avatarFileRef.current = null;\n    }\n  };\n\n  const handleDragOver = useCallback((e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n  }, []);\n\n  const handleDrop = useCallback(\n    (e: React.DragEvent) => {\n      e.preventDefault();\n      e.stopPropagation();\n\n      const file = e.dataTransfer.files[0];\n      if (file) {\n        handleAvatarSelect(file);\n      }\n    },\n    [handleAvatarSelect]\n  );\n\n  const handleFileInputChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const file = e.target.files?.[0];\n      if (file) {\n        handleAvatarSelect(file);\n      }\n    },\n    [handleAvatarSelect]\n  );\n\n  const handleSave = async () => {\n    setErrors({});\n\n    if (!formData.name.trim()) {\n      setErrors({ name: \"Name is required\" });\n      return;\n    }\n\n    if (!formData.email.trim()) {\n      setErrors({ email: \"Email is required\" });\n      return;\n    }\n\n    const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n    if (!emailRegex.test(formData.email)) {\n      setErrors({ email: \"Please enter a valid email address\" });\n      return;\n    }\n\n    if (formData.website && formData.website.trim()) {\n      try {\n        new URL(formData.website);\n      } catch {\n        setErrors({ website: \"Please enter a valid URL\" });\n        return;\n      }\n    }\n\n    setIsSaving(true);\n    try {\n      await onSave?.(formData);\n    } catch (error) {\n      setErrors({\n        _general:\n          error instanceof Error ? error.message : \"Failed to save profile\",\n      });\n    } finally {\n      setIsSaving(false);\n    }\n  };\n\n  const handleEmailChange = async () => {\n    setErrors({});\n\n    if (!emailChangeData.newEmail.trim()) {\n      setErrors({ newEmail: \"New email is required\" });\n      return;\n    }\n\n    const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n    if (!emailRegex.test(emailChangeData.newEmail)) {\n      setErrors({ newEmail: \"Please enter a valid email address\" });\n      return;\n    }\n\n    if (!emailChangeData.currentPassword.trim()) {\n      setErrors({ currentPassword: \"Current password is required\" });\n      return;\n    }\n\n    setIsChangingEmail(true);\n    try {\n      await onEmailChange?.(\n        emailChangeData.newEmail,\n        emailChangeData.currentPassword\n      );\n      setFormData((prev) => ({ ...prev, email: emailChangeData.newEmail }));\n      setEmailChangeData({ newEmail: \"\", currentPassword: \"\" });\n      setShowEmailChangeForm(false);\n      setErrors({});\n    } catch (error) {\n      setErrors({\n        emailChange:\n          error instanceof Error ? error.message : \"Failed to change email\",\n      });\n    } finally {\n      setIsChangingEmail(false);\n    }\n  };\n\n  const updateSocialLink = (platform: string, url: string) => {\n    setFormData((prev) => {\n      const socialLinks = prev.socialLinks || [];\n      const existingIndex = socialLinks.findIndex(\n        (link) => link.platform === platform\n      );\n      const updatedLinks = [...socialLinks];\n\n      if (url.trim()) {\n        if (existingIndex >= 0) {\n          updatedLinks[existingIndex] = { platform, url };\n        } else {\n          updatedLinks.push({ platform, url });\n        }\n      } else if (existingIndex >= 0) {\n        updatedLinks.splice(existingIndex, 1);\n      }\n\n      return { ...prev, socialLinks: updatedLinks };\n    });\n  };\n\n  const getSocialLink = (platform: string): string =>\n    formData.socialLinks?.find((link) => link.platform === platform)?.url || \"\";\n\n  return (\n    <Card className={cn(\"w-full shadow-xs\", className)}>\n      <CardHeader>\n        <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n          <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n            <CardTitle className=\"wrap-break-word\">Profile Settings</CardTitle>\n            <CardDescription className=\"wrap-break-word\">\n              Manage your profile information and avatar\n            </CardDescription>\n          </div>\n          <div className=\"flex shrink-0 gap-2\">\n            <Button\n              className=\"w-full sm:w-auto\"\n              disabled={isSaving}\n              onClick={handleSave}\n              type=\"button\"\n            >\n              {isSaving ? (\n                <>\n                  <Loader2 className=\"size-4 animate-spin\" />\n                  <span className=\"whitespace-nowrap\">Saving…</span>\n                </>\n              ) : (\n                <>\n                  <Save className=\"size-4\" />\n                  <span className=\"whitespace-nowrap\">Save Changes</span>\n                </>\n              )}\n            </Button>\n          </div>\n        </div>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col gap-6\">\n          {errors._general && (\n            <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-3\">\n              <p className=\"text-destructive text-sm\">{errors._general}</p>\n            </div>\n          )}\n\n          {/* Avatar Upload */}\n          <div className=\"flex flex-col gap-4\">\n            <FieldLabel>Profile Picture</FieldLabel>\n            <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n              <div\n                className={cn(\n                  \"relative flex size-24 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-full border-2 border-dashed transition-colors\",\n                  isUploadingAvatar\n                    ? \"border-primary bg-primary/5\"\n                    : \"border-muted bg-muted/30 hover:border-primary/50\"\n                )}\n                onClick={handleAvatarClick}\n                onDragOver={handleDragOver}\n                onDrop={handleDrop}\n              >\n                {avatarPreview ? (\n                  <>\n                    <Image\n                      alt=\"Profile avatar\"\n                      className=\"object-cover\"\n                      fill\n                      sizes=\"96px\"\n                      src={avatarPreview}\n                      unoptimized\n                    />\n                    {isUploadingAvatar && (\n                      <div className=\"absolute inset-0 flex items-center justify-center bg-background/80\">\n                        <Loader2 className=\"size-6 animate-spin text-primary\" />\n                      </div>\n                    )}\n                  </>\n                ) : (\n                  <Camera className=\"size-8 text-muted-foreground\" />\n                )}\n              </div>\n              <div className=\"flex flex-1 flex-col gap-2\">\n                <div className=\"flex flex-col gap-2 sm:flex-row\">\n                  <Button\n                    onClick={handleAvatarClick}\n                    type=\"button\"\n                    variant=\"outline\"\n                  >\n                    <Camera className=\"size-4\" />\n                    {avatarPreview ? \"Change Photo\" : \"Upload Photo\"}\n                  </Button>\n                  {avatarPreview && (\n                    <Button\n                      onClick={handleAvatarRemove}\n                      type=\"button\"\n                      variant=\"outline\"\n                    >\n                      <X className=\"size-4\" />\n                      Remove\n                    </Button>\n                  )}\n                </div>\n                <p className=\"text-muted-foreground text-xs\">\n                  Drag and drop an image here, or click to browse. Max size: 5MB\n                </p>\n                {errors.avatar && (\n                  <p className=\"text-destructive text-xs\">{errors.avatar}</p>\n                )}\n              </div>\n              <input\n                accept=\"image/*\"\n                className=\"hidden\"\n                onChange={handleFileInputChange}\n                ref={fileInputRef}\n                type=\"file\"\n              />\n            </div>\n          </div>\n\n          <Separator />\n\n          {/* Basic Information */}\n          <div className=\"flex flex-col gap-4\">\n            <Field>\n              <FieldLabel htmlFor=\"name\">\n                Name <span className=\"text-destructive\">*</span>\n              </FieldLabel>\n              <FieldContent>\n                <InputGroup>\n                  <InputGroupInput\n                    id=\"name\"\n                    onChange={(e) =>\n                      setFormData((prev) => ({ ...prev, name: e.target.value }))\n                    }\n                    placeholder=\"Your full name\"\n                    value={formData.name}\n                  />\n                </InputGroup>\n                {errors.name && <FieldError>{errors.name}</FieldError>}\n              </FieldContent>\n            </Field>\n\n            <Field>\n              <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n              <FieldContent>\n                <div className=\"flex flex-col gap-2\">\n                  <InputGroup>\n                    <InputGroupInput\n                      disabled={showEmailChangeForm}\n                      id=\"email\"\n                      onChange={(e) =>\n                        setFormData((prev) => ({\n                          ...prev,\n                          email: e.target.value,\n                        }))\n                      }\n                      placeholder=\"your.email@example.com\"\n                      type=\"email\"\n                      value={formData.email}\n                    />\n                  </InputGroup>\n                  {showEmailVerification && (\n                    <Button\n                      className=\"w-full sm:w-auto\"\n                      onClick={() =>\n                        setShowEmailChangeForm(!showEmailChangeForm)\n                      }\n                      type=\"button\"\n                      variant=\"outline\"\n                    >\n                      {showEmailChangeForm ? \"Cancel\" : \"Change Email\"}\n                    </Button>\n                  )}\n                </div>\n                {errors.email && <FieldError>{errors.email}</FieldError>}\n              </FieldContent>\n            </Field>\n\n            {showEmailChangeForm && (\n              <div className=\"flex flex-col gap-4 rounded-lg border bg-muted/30 p-4\">\n                <Field>\n                  <FieldLabel htmlFor=\"new-email\">\n                    New Email <span className=\"text-destructive\">*</span>\n                  </FieldLabel>\n                  <FieldContent>\n                    <InputGroup>\n                      <InputGroupInput\n                        id=\"new-email\"\n                        onChange={(e) =>\n                          setEmailChangeData((prev) => ({\n                            ...prev,\n                            newEmail: e.target.value,\n                          }))\n                        }\n                        placeholder=\"new.email@example.com\"\n                        type=\"email\"\n                        value={emailChangeData.newEmail}\n                      />\n                    </InputGroup>\n                    {errors.newEmail && (\n                      <FieldError>{errors.newEmail}</FieldError>\n                    )}\n                  </FieldContent>\n                </Field>\n\n                <Field>\n                  <FieldLabel htmlFor=\"current-password\">\n                    Current Password <span className=\"text-destructive\">*</span>\n                  </FieldLabel>\n                  <FieldContent>\n                    <InputGroup>\n                      <InputGroupInput\n                        id=\"current-password\"\n                        onChange={(e) =>\n                          setEmailChangeData((prev) => ({\n                            ...prev,\n                            currentPassword: e.target.value,\n                          }))\n                        }\n                        placeholder=\"Enter your current password\"\n                        type=\"password\"\n                        value={emailChangeData.currentPassword}\n                      />\n                    </InputGroup>\n                    {errors.currentPassword && (\n                      <FieldError>{errors.currentPassword}</FieldError>\n                    )}\n                    {errors.emailChange && (\n                      <FieldError>{errors.emailChange}</FieldError>\n                    )}\n                  </FieldContent>\n                </Field>\n\n                <Button\n                  className=\"w-full sm:w-auto\"\n                  disabled={isChangingEmail}\n                  onClick={handleEmailChange}\n                  type=\"button\"\n                >\n                  {isChangingEmail ? (\n                    <>\n                      <Loader2 className=\"size-4 animate-spin\" />\n                      Changing…\n                    </>\n                  ) : (\n                    \"Update Email\"\n                  )}\n                </Button>\n              </div>\n            )}\n\n            <Field>\n              <FieldLabel htmlFor=\"bio\">Bio</FieldLabel>\n              <FieldContent>\n                <Textarea\n                  id=\"bio\"\n                  onChange={(e) =>\n                    setFormData((prev) => ({ ...prev, bio: e.target.value }))\n                  }\n                  placeholder=\"Tell us about yourself...\"\n                  rows={4}\n                  value={formData.bio || \"\"}\n                />\n                <FieldDescription>\n                  A brief description about yourself (max 500 characters)\n                </FieldDescription>\n              </FieldContent>\n            </Field>\n\n            <Field>\n              <FieldLabel htmlFor=\"location\">Location</FieldLabel>\n              <FieldContent>\n                <InputGroup>\n                  <InputGroupInput\n                    id=\"location\"\n                    onChange={(e) =>\n                      setFormData((prev) => ({\n                        ...prev,\n                        location: e.target.value,\n                      }))\n                    }\n                    placeholder=\"City, Country\"\n                    value={formData.location || \"\"}\n                  />\n                </InputGroup>\n              </FieldContent>\n            </Field>\n\n            <Field>\n              <FieldLabel htmlFor=\"website\">Website</FieldLabel>\n              <FieldContent>\n                <InputGroup>\n                  <InputGroupInput\n                    id=\"website\"\n                    onChange={(e) =>\n                      setFormData((prev) => ({\n                        ...prev,\n                        website: e.target.value,\n                      }))\n                    }\n                    placeholder=\"https://example.com\"\n                    type=\"url\"\n                    value={formData.website || \"\"}\n                  />\n                </InputGroup>\n                {errors.website && <FieldError>{errors.website}</FieldError>}\n              </FieldContent>\n            </Field>\n          </div>\n\n          <Separator />\n\n          {/* Social Links */}\n          <div className=\"flex flex-col gap-4\">\n            <FieldLabel>Social Links</FieldLabel>\n            <div className=\"flex flex-col gap-3\">\n              {defaultSocialPlatforms.map((platform) => (\n                <Field key={platform.id}>\n                  <FieldLabel htmlFor={`social-${platform.id}`}>\n                    {platform.label}\n                  </FieldLabel>\n                  <FieldContent>\n                    <InputGroup>\n                      <InputGroupInput\n                        id={`social-${platform.id}`}\n                        onChange={(e) =>\n                          updateSocialLink(platform.id, e.target.value)\n                        }\n                        placeholder={platform.placeholder}\n                        type=\"url\"\n                        value={getSocialLink(platform.id)}\n                      />\n                    </InputGroup>\n                  </FieldContent>\n                </Field>\n              ))}\n            </div>\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "settings"
  ],
  "type": "registry:ui"
}