Skip to main content
The customer service exposes the signed-in customer. The service exposes one reactive signal and one fetch method.

Setup

import {
  provideContext,
  createCustomerService,
  createApiCustomerAdapter,
  CustomerServiceContext,
} from "@juo/blocks";

provideContext(
  root,
  CustomerServiceContext,
  createCustomerService(createApiCustomerAdapter({ baseUrl: "/api/v1" })),
);

Shape

type Customer = {
  id: string;
  name: string;
  email?: string;
  metadata?: Record<string, unknown>;
};

type CustomerService = {
  current: Signal<Customer | null>;
  getCurrent(): Promise<Result<Customer | null>>;
};

Reactive state

current is null until a successful call to getCurrent() populates it. After that, every block that reads it updates automatically.

Example: greeting block

import {
  defineBlock,
  effect,
  untracked,
  injectContext,
  CustomerServiceContext,
} from "@juo/blocks";

defineBlock("Greeting", {
  group: "theme",
  schema: { /* ... */ } as const,
  initialValue: () => ({ props: {} }),
  renderer(block) {
    const el = document.createElement("h1");
    const customer = injectContext(el, CustomerServiceContext);

    void customer.getCurrent();

    effect(() => {
      const name = customer.current.value?.name ?? "Guest";
      untracked(() => {
        el.textContent = `Welcome back, ${name}`;
      });
    });

    return el;
  },
});

Custom metadata

The metadata field is a free-form record. Adapters can populate it with store-specific fields (loyalty tier, plan flags) without changing the service interface.