Perfil

A seção Perfil das configurações de um produto de IA. Recorte uma foto em um círculo, escolha um nome de usuário que é verificado enquanto você digita, confirme um novo e-mail com um código de 6 dígitos, adicione links que reconhecem o site e veja um card ao vivo de como os outros veem você.

ProfileSettings é a página de Perfil de um produto como Claude ou Cursor, feita para se encaixar em qualquer seção do SettingsShell. Dê a ela o perfil salvo e um onSave, e ela cuida do resto: um rascunho que só salva quando você manda, erros de campo vindos das suas verificações ou do seu servidor e a barra de salvar flutuante do Settings com ⌘S.

No topo, um card mostra como os outros veem o seu perfil e muda conforme você digita: foto, nome, identificador, bio e links. Desligue Show my profile e o card escurece e diz que só você pode vê-lo. Copy link coloca a URL do perfil na área de transferência.

Escolha uma foto, ou solte uma na linha, e ela abre em um diálogo de recorte. Arraste para movê-la, amplie com o slider, a roda do mouse ou um pinch no trackpad, e a foto é recortada em um quadrado no navegador antes de chegar ao onSave como uma data URL. Arquivos do tipo errado, grandes demais ou com menos de 128 pixels são recusados na linha com o motivo.

O nome de usuário é verificado enquanto você digita, sem atrasar a digitação. As verificações esperam 350ms depois da última tecla, as obsoletas são canceladas por um AbortSignal, e nomes que você já verificou respondem instantaneamente. Um nome em uso mostra suas sugestões como correções de um toque. Se você salvar antes de uma verificação terminar, o salvamento espera por ela.

O e-mail não faz parte do rascunho. Change abre um diálogo de duas etapas: informe o novo endereço e depois o código de 6 dígitos enviado a ele, que se verifica sozinho quando o último dígito entra. Um código errado sacode e se limpa, Resend é liberado após 30 segundos, e a linha é atualizada quando o código é confirmado. Um endereço não verificado mostra Resend link.

Os links aceitam o que as pessoas colarem: github.com/you, uma URL completa ou um endereço www. Sites conhecidos ganham seu ícone, qualquer outro ganha a primeira letra. Ao salvar, são normalizados para URLs https e os vazios são descartados. Endereços que não são links ou que repetem outro são marcados um a um.

  1. Adicione o registro Pro ao components.json

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Adicione seu token

    Crie um token na sua página de conta e coloque-o em .env.local como HEXTAUI_PRO_TOKEN.

  3. Adicione o bloco

    pnpm dlx shadcn@latest add @hextaui-pro/profile

Conecte à sua API

Passe o perfil salvo e um onSave que retorna erros de campo ou lança um erro. checkUsername recebe um AbortSignal, então as verificações obsoletas são canceladas enquanto as pessoas digitam.

"use client"

import { SettingsSection, SettingsShell } from "../settings/settings"

import { ProfileSettings, type ProfileValues } from "@/components/blocks/profile/profile"

export function ProfilePage({ profile, email }: { profile: ProfileValues; email: string }) {
  return (
    <SettingsShell sections={[{ id: "profile", label: "Profile" }]} className="h-svh">
      <SettingsSection id="profile">
        <ProfileSettings
          profile={profile}
          email={email}
          checkUsername={async (username, signal) => {
            const response = await fetch(`/api/usernames/${username}`, { signal })
            return response.json()
          }}
          onSave={async (values) => {
            const response = await fetch("/api/profile", {
              method: "PATCH",
              body: JSON.stringify(values),
            })
            if (response.status === 409) return { username: "That username is taken." }
            if (!response.ok) throw new Error("Check your connection and try again.")
          }}
        />
      </SettingsSection>
    </SettingsShell>
  )
}

Alterar e verificar o e-mail

Adicione onRequestEmailCode e onVerifyEmailCode para ativar o botão Change. Retorne uma mensagem de onRequestEmailCode para recusar um endereço, e false de onVerifyEmailCode para um código errado.

"use client"

import * as React from "react"

import { SettingsSection, SettingsShell } from "../settings/settings"

import { ProfileSettings, type ProfileValues } from "@/components/blocks/profile/profile"

export function ProfileWithEmail({ profile, account }: {
  profile: ProfileValues
  account: { email: string; verified: boolean }
}) {
  const [email, setEmail] = React.useState(account.email)

  return (
    <SettingsShell sections={[{ id: "profile", label: "Profile" }]} className="h-svh">
      <SettingsSection id="profile">
        <ProfileSettings
          profile={profile}
          email={email}
          emailVerified={account.verified}
          onSave={(values) => fetch("/api/profile", { method: "PATCH", body: JSON.stringify(values) }).then(() => {})}
          onResendVerification={() => fetch("/api/email/verify", { method: "POST" })}
          onRequestEmailCode={async (next) => {
            const response = await fetch("/api/email/change", {
              method: "POST",
              body: JSON.stringify({ email: next }),
            })
            if (response.status === 409) return "That address belongs to another account."
            if (!response.ok) throw new Error("Couldn’t send the code. Try again.")
          }}
          onVerifyEmailCode={async (next, code) => {
            const response = await fetch("/api/email/change/verify", {
              method: "POST",
              body: JSON.stringify({ email: next, code }),
            })
            return response.ok
          }}
          onEmailChange={setEmail}
        />
      </SettingsSection>
    </SettingsShell>
  )
}

Anatomia

As partes que você compõe, de fora para dentro.

ParteDescrição
ProfileSettingsA seção: card de prévia, foto, nome, como o assistente chama você, nome de usuário, bio, perfil público, links e e-mail.
ProfileCardA prévia ao vivo do perfil público, com Copy link. Pode ser usada isoladamente.
AvatarCropDialogRecorta uma imagem em um quadrado com arrastar, zoom e teclado, e retorna uma data URL.
ChangeEmailDialogA alteração de e-mail em duas etapas: novo endereço e depois um código de 6 dígitos.
useUsernameCheckVerificações de disponibilidade de nome de usuário com debounce, canceláveis e em cache.

ProfileSettings

Use dentro de um SettingsSection.

PropTipoPadrão
profileO que está salvo agora: { avatar, name, nickname, username, bio, links, public }. Quando ele muda e não há edições, o rascunho o acompanha.
ProfileValues–
onSaveChamado com os valores aparados e os links normalizados. Retorne { field: message } para erros de campo, ou lance um erro para mostrar a mensagem na barra de salvar.
(values) => void | errors | Promise<void | errors>–
emailO endereço de login.
string–
emailVerifiedMostra Verified, ou Not verified com Resend link.
booleantrue
checkUsernameVerifica se um nome de usuário está livre. Recebe o nome em minúsculas e um AbortSignal. Sem isso, apenas o formato é verificado.
(username, signal) => Promise<{ available, message?, suggestions? }>–
onRequestEmailCodeEnvia um código a um novo endereço. Retorne uma mensagem para recusar o endereço. Com onVerifyEmailCode, ativa o Change.
(email) => Promise<void | string>–
onVerifyEmailCodeVerifica o código. Retorne false para um código errado, ou lance um erro para mostrar a sua mensagem.
(email, code) => Promise<boolean>–
onEmailChangeChamado quando o novo endereço é confirmado.
(email) => void–
onResendVerificationAdiciona Resend link enquanto o endereço não está verificado.
() => Promise<unknown>–
profileUrlA URL do perfil público, exibida sob o nome de usuário e copiada por Copy link.
(username) => stringhttps://hexta.app/@username
assistantNameUsado em "Como o Hexta deve chamar você?".
string"Hexta"
maxAvatarBytesMaior foto aceita.
number5 MB
maxLinksQuantos links um perfil pode ter.
number5
maxBioTamanho da bio, com um contador.
number160
PropTipoPadrão
profileO que mostrar.
{ avatar, name, username, bio, links, public }–
urlA URL do perfil a exibir e copiar.
string–
onCopiedChamado depois que o link é copiado.
() => void–
PropTipoPadrão
sourceA imagem a recortar. Aberto enquanto definida.
{ url, width, height } | null–
onOpenChangeLimpa a origem quando ele fecha.
(open) => void–
onApplyObtém o quadrado recortado em WebP, ou em PNG onde o WebP não é suportado.
(dataUrl) => void–
outputSizeMaior lado da saída em pixels. Nunca amplia além do recorte.
number512
maxZoomAté onde você pode ampliar.
number4
PropTipoPadrão
openSe está aberto.
boolean–
currentEmailExibido na introdução e recusado como novo endereço.
string–
onRequestCodeEnvia o código. Retorne uma mensagem para recusar.
(email) => Promise<void | string>–
onVerifyCodeVerifica o código.
(email, code) => Promise<boolean>–
onChangedChamado depois que o código é confirmado, antes de o diálogo fechar.
(email) => void–
resendAfterSegundos antes de Resend code ser liberado.
number30

useUsernameCheck(username, options)

Retorna { status, result, problem, retry, resolve }. status é idle, invalid, checking, available, taken ou error.

PropTipoPadrão
currentO nome de usuário salvo, que nunca é verificado.
string–
checkSua verificação de disponibilidade.
(username, signal) => Promise<UsernameCheck>–
delayMilissegundos de espera depois que a digitação para.
number350
TeclaAção
EnterNo último link, adiciona outro link e vai até ele.
←→↑↓No diálogo de recorte, move a foto. Shift move mais longe.
+−No diálogo de recorte, aumenta e diminui o zoom. 0 redefine.
⌘SSalva enquanto algo não está salvo. Ctrl+S no Windows e no Linux.
EscFecha um diálogo e devolve o foco ao botão que o abriu.
  • Cada campo é nomeado por sua linha, e os botões que repetem uma palavra dizem em que atuam: Upload photo, Change email, Remove link 2, github.com/you.
  • Os resultados do nome de usuário, os links copiados, as mudanças de foto, os links removidos e um e-mail alterado são anunciados de forma polite. A verificação em si não é anunciada, para que a digitação continue silenciosa.
  • Os erros de campo marcam apenas o campo com problema, e o foco vai para o primeiro quando um salvamento é recusado. O código do e-mail anuncia verificando, confirmado e errado.
  • Remover um link move o foco para o próximo link, ou para Add link quando não sobra nenhum. Remover a foto move o foco para Upload.
  • A área de recorte é focalizável e funciona com as setas e as teclas de mais e menos, e o slider de zoom informa seu valor como porcentagem.
  • Em telas de toque, os inputs usam texto de 16px para que o iOS não dê zoom, e os botões de ícone têm alvos de 44px.

Construído com

Os componentes gratuitos do HextaUI de que Profile é feito. Cada um é instalado separadamente.

Código

6 arquivos, adicionados a components/blocks/profile.