Agent Profile
The Panda websites profile page (/profile, /about) or a team member page (/team/<slug>): the About Moderna hero (languages eyebrow, uppercase name with a side rule, title, email, phone, social links, full bio beside the…
Anatomy
Every piece of Agent Profile as it nests in code. A root holds the data and state; its parts and hooks only work inside it.
<PandaProvider>Mounted once in app/layout.tsx<AgentProfile />complete widgetThe Panda websites profile page (/profile, /about) or a team member page (/team/<slug>): the About Moderna hero (languages eyebrow,…
<AgentAbout />complete widgetThe Panda websites "About Moderna" block on its own (homepage About section): languages eyebrow, name, title, contacts, social links and…
</PandaProvider>Installation
- 1npm install <tarball or @pandaidx/widgets>Terminalnpm install https://github.com/pandaidx/panda-widgets/releases/download/v1.14.1/pandaidx-widgets-1.14.1.tgz
- 2import "@pandaidx/widgets/styles.css" in app/layout.tsx
- 3Wrap the site in <PandaProvider publicKey apiUrl preset theme> (app/layout.tsx)
- 4In each page (Server Component) call the widget's server loader, then render the widget with its data
- 5Allow the images: next.config images.remotePatterns [{ protocol: "https", hostname: "**" }] (listing and blog photos come from several CDNs)
Usage
// app/profile/page.tsximport { notFound } from "next/navigation"import { AgentProfile } from "@pandaidx/widgets/client"import { fetchAgentProfile } from "@pandaidx/widgets/server"export default async function ProfilePage({ searchParams }: { searchParams: Promise<{ listings?: string; inventory?: string; status?: string; page?: string }> }) {const data = await fetchAgentProfile({ publicKey: process.env.NEXT_PUBLIC_PANDA_PUBLIC_KEY!, apiUrl: process.env.NEXT_PUBLIC_PANDA_API_URL, searchParams: await searchParams })if (!data) notFound()return <AgentProfile data={data} />}// app/team/[slug]/page.tsx: fetchAgentProfile({ ..., member: slug, searchParams }) → <AgentProfile data={data} />
Examples
A profile with its own hero, inventory tabs and reviews
// app/profile/page.tsximport { notFound } from "next/navigation"import { fetchAgentProfile } from "@pandaidx/widgets/server"import { ProfileView } from "@/components/profile-view"export default async function ProfilePage({ searchParams }: { searchParams: Promise<Record<string, string | string[] | undefined>> }) {const data = await fetchAgentProfile({ publicKey: process.env.NEXT_PUBLIC_PANDA_PUBLIC_KEY!, apiUrl: process.env.NEXT_PUBLIC_PANDA_API_URL, searchParams: await searchParams })if (!data) notFound()return <ProfileView data={data} />}// components/profile-view.tsx"use client"import Link from "next/link"import { ListingGridCard, ListingGridRoot, MlsDisclaimers, useAgentProfile, useListingGrid, type AgentProfileData } from "@pandaidx/widgets/client"const Cards = () => {const grid = useListingGrid()return (<><div className="grid gap-6 md:grid-cols-3">{grid.listings.map((listing) => <ListingGridCard key={listing._id} listing={listing} />)}</div><MlsDisclaimers className="mt-10" /></>)}export const ProfileView = ({ data }: { data: AgentProfileData }) => {const profile = useAgentProfile({ data })return (<main className="mx-auto max-w-6xl px-4 py-16"><h1 className="text-4xl">{profile.name}</h1>{profile.biography.map((text) => <p key={text} className="mt-4">{text}</p>)}{profile.emailHref ? <a href={profile.emailHref}>{profile.email}</a> : null}{profile.listings && profile.inventory ? (<section className="mt-12">{profile.hasOffice ? <div id="office-listings" /> : null}<nav id="listings" className="mb-6 flex gap-4">{profile.inventory.tabs.map((tab) => <Link key={tab.href} href={tab.href} aria-current={tab.selected}>{tab.label}</Link>)}</nav><ListingGridRoot data={profile.listings}><Cards /></ListingGridRoot></section>) : null}</main>)}
API reference
AgentProfile
The Panda websites profile page (/profile, /about) or a team member page (/team/<slug>): the About Moderna hero (languages eyebrow, uppercase name with a side rule, title, email, phone, social links, full bio beside the portrait or a YouTube play overlay, on the agent's background color with contrasting text), the MLS inventory (ListingGrid: Personal/Office menu, Active/Sold tabs, pages of 20; a member's Active/Sold listings), the videos band (VideoGallery), the black quote band with a Contact button and the Client Reviews grid (five stars).
Import from @pandaidx/widgets/client · renders inside PandaProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| data* | object | — | Owner profile or team member page (null → notFound()). |
| sections | "about" | "listings" | "videos" | "quote" | "reviews" | ["about","listings","videos","quote","reviews"] | Blocks to render (websites order kept). A member page only has about + listings. |
| profileTheme | "moderna" | "moderna" | About hero design (the websites profile theme). |
| contactHref | string | "/contact" | Link of the quote band's Contact button. |
| contactLabel | string | "Contact" | Text of the quote band button. |
| videosTitle | string | "Videos" | Heading of the videos band. |
| reviewsTitle | string | "Client Reviews" | Heading of the reviews band. |
| reviewsId | string | "reviews" | Id of the reviews band (target of the About "Client Reviews" buttons). |
| preset | "default" | "template1" | — | Preset for this widget only (defaults to the provider preset). |
| variant | "default" | "template1" | — | Deprecated alias of preset. |
| theme | object | — | Token overrides for this widget only, merged over the provider theme (same shape as PandaProvider.theme). |
AgentAbout
The Panda websites "About Moderna" block on its own (homepage About section): languages eyebrow, name, title, contacts, social links and the bio (cut at 800 characters with View Profile / Client Reviews buttons in section, full in hero) beside the portrait or a YouTube play overlay; background from the dashboard with contrasting text.
Import from @pandaidx/widgets/client · renders inside PandaProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| person* | object | — | Name, title, email, phones, links, biography, portraits, spoken languages. |
| section | object | — | Background color, image (removed → text full width), fit, side and YouTube video. |
| layout | "section" | "hero" | "section" | Homepage section (bio cut + buttons) or profile hero (full bio). |
| profileHref | string | "/profile" | View Profile link. |
| reviewsHref | string | "/profile#reviews" | Client Reviews link. |
| bioLength | number | 800 | section: characters of the bio before "...". |
| preset | "default" | "template1" | — | Preset for this widget only (defaults to the provider preset). |
| variant | "default" | "template1" | — | Deprecated alias of preset. |
| theme | object | — | Token overrides for this widget only, merged over the provider theme (same shape as PandaProvider.theme). |
Hooks
useAgentProfile()inside PandaProvideruseAgentProfile({ data }) → { kind: "owner" | "member", member, name, title, biography (paragraphs), headshot, image, spokenLanguages, email, emailHref, phones, phoneHref, socials: { network, href }[] (facebook, instagram, twitter, linkedin, youtube, tiktok, whatsapp, website; ready links), quote (owner only), section (About hero background / image / video settings), listings (the MLS grid data: draw it with <ListingGridRoot data={listings}>), status ("active" | "sold"), inventory: { sourceMenu?: { label, options: { href, label, selected }[] }, tabs: { href, label, selected }[], pagination?: { prevHref, nextHref, prevLabel, nextLabel } } | null (owner: the Personal / Office menu, Active / Sold tabs and Prev / Next links of the website, in the site's language; null on a member page), available, hasOffice (render <div id="office-listings" /> before the grid: the Office tabs jump to it), videos, testimonials, teammates } — data comes from fetchAgentProfile (member: <slug> for /team/<slug>; null → notFound()). Never rebuild the inventory links or read ?listings= yourself: the inventory links already follow the website's rules. AgentProfile stays the ready-made page. Client component only.
Server data
Loaders run in a Server Component and come from @pandaidx/widgets/server. Typed data is for a page you design yourself, rendered inside the root.
fetchAgentProfile()widget loaderThe owner's /profile (/v1/site profile + About settings, the MLS inventory page picked by searchParams, /v1/videos, /v1/testimonials) or, with member, a team member (/v1/team/:slug + up to 500 listings of the ?listings= status, person.slug / person.group, inventory.available = which of Active / Sold have listings so a page can render its own tabs, and teammates = the other members from /v1/team). Null when the site / member is missing or the team page is off. Returns AgentProfileData | null → data