Ekip

Bir yapay zeka ürününün çalışma alanı ayarları için ekip üyeleri ve davetler. Yazdığınız davetleri önizleyen bir koltuk ölçeri, yapıştırılan listeyi denetlenmiş çiplere çeviren bir davet kutusu, rolleri, arama, filtre ve sıralaması olan üyeler, yeniden gönderme bekleme süreli ve kopyalanabilir bağlantılı bekleyen davetler, yazılı onaylı sahiplik devri ve geri alınabilir kaldırma.

Çalışma alanlı her yapay zeka ürünü aynı sayfaya sahip olur: ekipte kimlerin olduğu, her kişinin neler yapabildiği, kimlerin davet edildiği ve kaç koltuk kaldığı. Team, bu sayfanın bir ayarlar bölümüne ekleyeceğiniz tek bir bileşen halidir. Üyelerinizi, davetlerinizi ve koltuk sayınızı okur ve biri bir eylem yaptığında işleyicilerinizi çağırır. Veriyi asla kendiliğinden değiştirmez; bu yüzden gösterdiği şey her zaman sunucunuzun söylediğidir.

Davet kutusu adresleri insanların gerçekten sahip olduğu biçimde alır. Yazıp virgül, boşluk veya Enter'a basın ya da bir elektronik tablodan bir sütun, bir e-postadan bir To: satırı veya Ana Lima <[email protected]> gibi adlı bir liste yapıştırın. Her adres bir çip olur. Yinelenenler atılır, yapıştırırken adres olmayan kelimeler atlanır ve geçersiz adresler, mevcut üyeler ve zaten daveti olan kişiler için çipler nedenle işaretlenir; ayrıca hepsini tek seferde kaldıran bir düğme vardır. Rolü seçin, isteğe bağlı bir not ekleyin ve gönderin.

Koltuk ölçeri, üyeleri, bekleyen davetleri ve yazdığınız çipleri üç katman olarak gösterir; böylece bir davetin maliyetini göndermeden önce görürsünüz. Süresi dolmuş davetler koltuk tutmaz. Davetler limiti aşacaksa gönderme, kaç tanesinin kaldırılacağı belirtilerek engellenir ve limitte ölçer bir Add seats çağrısına dönüşür.

Her üye satırında kişinin rolü, her rolün neler yapabildiğini gösteren bir menü olarak bulunur. Güvenlik önlemleri yerleşiktir: tek sahip, sahipliği devretmeden kendi rolünü düşüremez veya ayrılamaz ve menüler seçeneği sessizce gizlemek yerine bunu söyler. Yöneticiler sahiplere dokunamaz. Birini sahip yapmak ya da kendi rolünüzü düşürmek önce sorar. Sahipliği devretmek yeni sahibin e-postasını yazmanızı ister; böylece yanlış Ana anahtarları asla almaz.

Bekleyen davetler ne zaman gönderildiklerini, süresinin ne zaman dolacağını ve ne zaman dolduğunu gösterir. Resend bağlantıyı yeniler ve son gönderimden sayılan bir bekleme süresi başlatır; böylece geri sayım yeniden yüklemeden sağ çıkar. Copy invite link gerçek bağlantıyı panoya koyar. Bir üyeyi kaldırmak bir kez sorar, ardından Undo sunar ve odak sayfanın başına düşmek yerine sonraki kişiye geçer.

  1. Pro registry'yi components.json dosyasına ekleyin

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Token'ınızı ekleyin

    Hesap sayfanızda bir token oluşturun ve .env.local dosyasına HEXTAUI_PRO_TOKEN olarak ekleyin.

  3. Bloğu ekleyin

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

API'nize bağlayın

TeamSettings kontrollüdür: members, invites ve seats iletin ve her işleyicide güncelleyin. Sunucunuzun reddettiği adresleri her çipte nedeniyle korumak için onInvite'tan { email: message } döndürün.

"use client"

import * as React from "react"

import { SettingsSection, SettingsShell } from "../settings/settings"
import { TeamSettings, type TeamInvite, type TeamMember } from "@/components/blocks/team/team"

type Team = { seats: number; members: TeamMember[]; invites: TeamInvite[] }

async function call<Result = void>(path: string, init?: RequestInit) {
  const response = await fetch(path, {
    ...init,
    headers: { "content-type": "application/json" },
  })
  if (!response.ok) throw new Error((await response.text()) || "Try again in a moment.")
  return (response.status === 204 ? undefined : await response.json()) as Result
}

export function TeamPage({ team: initial, userId }: { team: Team; userId: string }) {
  const [team, setTeam] = React.useState(initial)
  const reload = async () => setTeam(await call<Team>("/api/team"))

  return (
    <SettingsShell sections={[{ id: "team", label: "Members" }]} className="h-svh">
      <SettingsSection id="team">
        <TeamSettings
          members={team.members}
          invites={team.invites}
          seats={team.seats}
          currentUserId={userId}
          onInvite={async (request) => {
            const { errors } = await call<{ errors: Record<string, string> }>("/api/team/invites", {
              method: "POST",
              body: JSON.stringify(request),
            })
            await reload()
            return errors
          }}
          onRoleChange={async (id, role) => {
            await call(`/api/team/members/${id}`, { method: "PATCH", body: JSON.stringify({ role }) })
            await reload()
          }}
          onRemove={async (id) => {
            await call(`/api/team/members/${id}`, { method: "DELETE" })
            await reload()
          }}
          onResend={async (id) => {
            await call(`/api/team/invites/${id}/resend`, { method: "POST" })
            await reload()
          }}
          onRevoke={async (id) => {
            await call(`/api/team/invites/${id}`, { method: "DELETE" })
            await reload()
          }}
          upgradeHref="/billing?add=seats"
        />
      </SettingsSection>
    </SettingsShell>
  )
}

Yalnızca izin verdikleriniz

Her eylem yalnızca işleyicisi ayarlıysa ve yalnızca yapmasına izin verilenler için görünür. onInvite olmadan davet kutusu yoktur ve üyeler ile görüntüleyenler listeyi menüsüz görür.

"use client"

import { TeamSettings, type TeamMember } from "@/components/blocks/team/team"

const members: TeamMember[] = [
  { id: "u1", name: "Mia Chen", email: "[email protected]", role: "owner", lastActiveAt: Date.now() },
  { id: "u2", name: "Jonas Weber", email: "[email protected]", role: "admin", lastActiveAt: null },
]

export function ReadOnlyTeam({ userId }: { userId: string }) {
  return <TeamSettings members={members} currentUserId={userId} seats={5} />
}

export function OwnerTeam({ userId }: { userId: string }) {
  return (
    <TeamSettings
      members={members}
      currentUserId={userId}
      seats={5}
      onRoleChange={async () => {}}
      onRemove={async () => {}}
      onTransferOwnership={async (id) => {
        await fetch(`/api/team/owner`, { method: "PUT", body: JSON.stringify({ id }) })
      }}
    />
  )
}

Bir kaldırmayı geri al

onRestore iletin; bir kaldırmadan sonraki toast, kaldırılan üyeyi geri eklemeniz için size veren bir Undo düğmesi alır.

"use client"

import * as React from "react"

import { TeamSettings, type TeamMember } from "@/components/blocks/team/team"

export function TeamWithUndo({ initial, userId }: { initial: TeamMember[]; userId: string }) {
  const [members, setMembers] = React.useState(initial)

  return (
    <TeamSettings
      members={members}
      currentUserId={userId}
      onRemove={async (id) => {
        await fetch(`/api/team/members/${id}`, { method: "DELETE" })
        setMembers((current) => current.filter((member) => member.id !== id))
      }}
      onRestore={async (member) => {
        await fetch(`/api/team/members`, { method: "POST", body: JSON.stringify(member) })
        setMembers((current) => [...current, member])
      }}
    />
  )
}

Yapı

Dıştan içe doğru birleştirdiğiniz parçalar.

ParçaAçıklama
TeamSettingsTüm sayfa: koltuklar, davet kutusu, bekleyen davetler, üyeler ve isteğe bağlı alan adı otomatik katılma satırı. Bir SettingsSection içine yerleştirin.
parseEmailListDavet kutusunun arkasındaki ayrıştırıcı. Yapıştırılan veya yazılan metni küçük harfli adreslere böler, Name <email> ve mailto: biçimlerini açar ve yapıştırırken adres olmayan kelimeleri atlar.
teamRolesDört rol, etiketleri ve açıklamalarıyla, en çok erişimden en aza doğru sırayla.
PropTürVarsayılan
members{ id, name, email, role, avatarUrl?, lastActiveAt?, joinedAt? }. lastActiveAt null ise Not yet active gösterir.
TeamMember[]–
invites{ id, email, role, sentAt, expiresAt, link? }. Zamanlar bir Date, ISO dizesi veya milisaniye kabul eder. link, Copy invite link'i etkinleştirir.
TeamInvite[][]
currentUserIdOturum açmış üye. Rolü hangi eylemlerin görüneceğini belirler.
string–
seatsPlandaki koltuklar. Üyeler ve süresi dolmamış davetler birer tane kullanır. Sınırsız koltuk için atlayın.
number–
domainDoğrulanmış bir şirket e-postası olan kişilerin davetsiz katılmasına izin veren bir satır gösterir.
{ name, verified, autoJoin, role? }–
resendCooldownBir davet gönderildikten sonra yeniden gönderilebilmesi için geçmesi gereken saniye.
number60
pageSizeShow more'dan önce kaç üyenin gösterileceği.
number50
onInviteDavet kutusunu ekler. Sunucunuzun reddettiği adresler için e-postaya göre mesajlar döndürün; bunlar mesajla birlikte çip olarak kalır. Her çipi korumak ve hatayı düğmede göstermek için hata fırlatın.
({ emails, role, note }) => void | { [email]: message } | Promise–
onRoleChangeRol menüsünü ekler. Satır, çözülene kadar bir spinner ve hata fırlatırsa bir toast gösterir.
(memberId, role) => void | Promise–
onRemoveRemove from team ve kendi satırınızda Leave team ekler. Hata fırlatırsa iletişim kutusu mesajla açık kalır.
(memberId) => void | Promise–
onRestoreBir kaldırmadan sonra toast'a, üyeyi eski haliyle geri getiren Undo ekler.
(member) => void | Promise–
onTransferOwnershipSahipler için, yeni sahibin e-postası yazılarak onaylanan Transfer ownership ekler.
(memberId) => void | Promise–
onResendResend ekler. Bekleme süresinin başlaması ve bağlantının yenilenmesi için sentAt ve expiresAt değerlerini güncelleyin.
(inviteId) => void | Promise–
onRevokeRevoke invite ekler.
(inviteId) => void | Promise–
onAutoJoinChangeAlan adı anahtarını çalıştırır. Bu hata fırlatırsa sallanır ve yerinde kalır.
(enabled) => void | Promise–
onUpgradeTüm koltuklar dolduğunda ana eylem olan bir Add seats düğmesi ekler.
() => void–
upgradeHrefAdd seats'i bunun yerine bir bağlantı yapar.
string–
classNameSarmalayıcı için sınıflar, bir grup sütunu.
string–

parseEmailList

{ emails, skipped } döndürür.

PropTürVarsayılan
textVirgül, noktalı virgül, boşluk, sekme veya yeni satırlarla ayrılmış adresler içeren herhangi bir şey.
string–
options.pasteEn az bir adres varken @ içermeyen kelimeleri atlar ve skipped içinde sayar.
booleanfalse
TuşAction
EnterDavet kutusunda yazdığınızı bir çipe çevirir. Göndermek için boş kutuda yeniden basın.
,Ayrıca yazdığınızı bir çipe çevirir; boşluk ve noktalı virgül de öyle.
BackspaceBoş bir davet kutusunda son çipe geçer. Orada Backspace veya Delete onu kaldırır ve öncekine geçer.
⌘EnterDavetleri adres kutusundan ya da nottan gönderir. Windows ve Linux'ta Ctrl+Enter.
EscapeÜye aramasını temizler ya da açık bir menüyü veya iletişim kutusunu kapatır.
  • Üye satırları kişinin adını taşıyan liste öğeleridir. Rol menüleri "Admin, change role for Ana Lima", daha fazla düğmesi ise "More actions for Ana Lima" gibi adlandırılır.
  • Gönderilemeyen çipler nedenlerini adlarında taşır ve kutunun altındaki sorun listesi input'a açıklaması olarak bağlanır.
  • Adres ekleme, gönderme, rol değişiklikleri ve kaldırmalar tek bir live region'da polite olarak duyurulur. İletişim kutularındaki hatalar alert olarak duyurulur.
  • Devre dışı eylemler kendilerini açıklar: rol menüsü ve daha fazla menüsü, tek sahibin neden rolünü değiştiremeyeceğini veya ayrılamayacağını söyler ve Resend, adında kalan süreyle bekleme süresi boyunca odaklanabilir kalır.
  • Bir kaldırmadan sonra odak sonraki üyenin eylemlerine, listenin sonundaysa öncekine geçer. Bir daveti iptal etmek davetler için aynısını yapar.
  • Koltuk ölçeri, etiketi ve sayısı metin olarak bir meter'dır. Azaltılmış harekette kaymadan değişir.

Şunlarla geliştirildi

Team bloğunun yapıldığı ücretsiz HextaUI bileşenleri. Her biri tek başına yüklenir.

Kod

3 dosya, components/blocks/team konumuna eklendi.