Skip to main content
Consero

Consero curates invitation-only executive events. I designed and built the platform that replaced fragmented Salesforce pages, manual PDFs, and a legacy mobile app, supporting attendees and sponsors across three brands.

Product Design, Design System, Front-end Development, React Query, Next.js

Consero

The Challenge

Attendees expect the product to work like the apps they use every day. Event teams needed to run registration, scheduling, meeting requests, logistics, and agenda production without losing Salesforce as the system of record.

The product had to make complex workflows feel simple, under real constraints:

  • 300+ attendees per event, multiple events running at once
  • Multiple user types with different permissions and workflows
  • Salesforce-backed API responses often exceeding 1 second
  • Unreliable Wi-Fi in event venues
  • Three branded experiences, one codebase

Mapping the Event Lifecycle

Before designing screens or writing code, I talked to attendees and sponsors and mapped their journeys from start to finish. This helped us see where people needed more context, where the experience could stay simple, and which moments had to feel fast.

Three principles came out of it:

  • Wayfinding: Use visual cues to guide attention as the event progresses.
  • Feedback: Update immediately, then roll back and explain what happened if something fails.
  • Anticipation: Preload the data people are most likely to need next.
Consero event lifecycle mapA flow diagram of the full event experience, from invitation and registration through profile setup, meeting preferences, scheduling, on-site check-in, itineraries, and post-event feedback. Branches show where attendee, partner, and operations paths diverge, and which steps depend on Salesforce data or open only after a specific event milestone.

System Architecture

Browser requests pass through Next.js route handlers, which call the APEX API using server-held credentials. Salesforce stays behind that boundary, so the UI can behave like a real product instead of a thin wrapper around it.

BrowserNext.js App Router/api/v2 routesdomain fetchersAPEX APISalesforce

Requests can fail for several reasons: an expired session, missing permissions, an invalid request body, or an unexpected Salesforce response. Without a shared error model, every screen has to interpret those failures differently, making product states harder to design and test.

Request
Parse params / body
Require session
Call domain fetcher
INVALID_SESSION_ID? Refresh token, retry once
Map domain error
Product-safe response
return Result.gen(async function* () {
  const config = yield* getAuthConfig();
  const session = yield* requireUserSessionWithType(rawSession);
  const attendeeDetails = yield* Result.await(parseRequestBody(request));

  const attendee = yield* Result.await(
    postAttendeeDetails({
      accessToken: session.accessToken,
      config,
      eventId,
      profileId,
      body: attendeeDetails,
    }),
  );

  return Result.ok(attendee);
});

Contextual Dashboard

Attendees needed one place to see what to do next. The tasks shown depend on the event timeline, attendee type, completion status, and data from Salesforce. Some actions only become available before or after a specific milestone.

I designed the dashboard around four explicit task states:

UnavailableActiveCompletedEditable

Unavailable tasks have less visual emphasis and show when they will open. Active tasks stand out as the next action to take. Completed tasks confirm progress, while editable tasks let attendees return and update their information when needed.

Consero
Consero

Metadata-Driven Forms

Each event needs its own mix of forms: registration, profiles, goals, feedback, logistics, and partner requests. The first version of the Form Builder worked, but one component was responsible for nearly everything: rendering, validation, navigation, autosave, mutations, uploads, and every edge case.

I refactored it into a feature-local compound component with a small public API, a view model that manages the workflow, and a component structure that is easy to follow:

<FormBuilder.Root>
  <FormBuilder.Sidebar />
  <FormBuilder.Main>
    <FormBuilder.StepHeader />
    <FormBuilder.Form>
      <FormBuilder.Fields />
      <FormBuilder.Footer />
    </FormBuilder.Form>
    <FormBuilder.MobileLiaisons />
  </FormBuilder.Main>
</FormBuilder.Root>

Salesforce metadata maps to reusable field renderers grouped by responsibility: basic inputs, choices, file uploads, session feedback, and partner lists.

The view model manages the active step, dirty sections, autosave, validation, and focus on the first invalid field. React Query handles mutations, rollback, and cache invalidation.

Read the deep dive
Consero
Consero

Composable Tables

Preference ranking is the most demanding table workflow in the product. It combines large sets of companies or attendees with search, sorting, filters, sticky controls, drafts, and submission. The same table patterns also support check-in lists, itineraries, attendee directories, and session rosters.

I used PreferenceTable to establish a shared structure: root, header, body, and row. Each workflow then adds the behavior it needs: ranking and drafts for preferences, status updates for check-in, and schedule details for itineraries.

Consero
Consero

Fast Navigation Over Slow APIs

Salesforce-backed requests can take more than a second, and venue Wi-Fi can make them even slower. During a live event, attendees opening their itinerary still expect the product to respond immediately. I couldn’t make every upstream request fast, so I made the most common navigation paths feel fast.

React Query prefetches data for likely next routes before the attendee clicks, using centralized query keys:

queryClient.prefetchQuery({
  queryKey: qk.event.attendeeDetails(eventId, profileId),
  queryFn: () => fetchAttendeeDetails({ eventId, profileId }),
  staleTime: 5 * 60 * 1000,
});

When the data is already cached, the next screen can render in roughly 50 milliseconds. Requests that previously took more than a second become invisible on common paths. Repeat visits reuse the same data, while the qk factory keeps cache invalidation consistent.

Consero
Consero
Consero

Spark Design System

Spark is the design system I built to carry the Consero aesthetic across the product without tying components to a single brand.

Semantic tokens define product roles for surfaces, text, borders, icons, focus, and status. Each brand supplies its own values through data-brand-theme, allowing Consero, Eaton Hall, and Quartz to share the same components while keeping distinct identities.

Spark includes more than 75 reusable elements documented across 50 pages. The system builds from small foundations into complete product experiences:

primitivecomponentpatternblockscreen

Consero Spark
Consero Spark Button

One Product, Three Themes

Each brand owns its neutral and accent scales, with dedicated light and dark modes, then maps them to tokens like surface-page, text-muted, border-brand, focus, and status-success.

The root sets a single data-brand-theme, while components keep the same markup and reference semantic tokens instead of brand-specific colors.

/* Palette → semantic roles */
[data-brand-theme="quartz"] {
  --brand-neutral-0: 255 255 255;
  --brand-neutral-500: 30 24 42;
  --brand-neutral-900: 12 0 36;
  --brand-accent-500: 99 37 255;

  --surface-control-selected: var(--brand-accent-500);
  --text-on-brand: var(--brand-neutral-0);
  --border-brand: var(--brand-accent-500);
  --focus: var(--brand-accent-500);
}

/* Mode-specific overrides */
.dark[data-brand-theme="quartz"] {
  --surface-row: var(--brand-neutral-900);
  --surface-dialog: var(--brand-neutral-500);
  --text: var(--brand-neutral-0);
}

Consero, Eaton Hall, and Quartz can share the same workflows while still feeling like distinct products. New components inherit the right theme automatically, and brand updates happen once at the token layer instead of throughout the product.

Quartz dashboard in dark mode
Interactive comparison of the Consero, Eaton Hall, and Quartz brand themes in light and dark modes.

Outcomes

The platform is now the starting point for every event across Consero, Eaton Hall, and Quartz. Attendees use one product for registration, schedules, meeting preferences, and logistics. The legacy mobile app has been retired, along with the PDFs, spreadsheets, and email chains that operations teams previously managed by hand.

4x

Faster event setup. Operations teams launch registration, agendas, and logistics in days, not weeks

8k+

Attendees served across three brands, all on one codebase

80%

Of manual event workflows eliminated. PDF agendas, spreadsheets, and email chains retired

PWA

Installs from the browser to the home screen. The legacy native app is retired, with nothing to download or update

Want to work together?