Skip to main content
The schedules service manages the calendar of upcoming orders attached to a subscription. Blocks use it to render the next delivery, skip a cycle, or move a date.

Setup

import {
  provideContext,
  createSchedulesService,
  createApiSchedulesAdapter,
  SchedulesServiceContext,
} from "@juo/blocks";

provideContext(
  root,
  SchedulesServiceContext,
  createSchedulesService(createApiSchedulesAdapter({ baseUrl: "/api/v1" })),
);

Shape

type ScheduleOrder = {
  id: string;
  subscriptionId: string;
  deliveryDate: string;
  items: ScheduleOrderItem[];
  status: "scheduled" | "processing" | "delivered" | "skipped";
};

type SchedulesService = {
  /** Reactive signal — shared upcoming orders for multi-block access. */
  upcoming: Signal<ScheduleOrder[]>;
  /** Sourced from SubscriptionService — guards billing-sensitive actions. */
  hasPendingBilling: Signal<boolean>;

  getUpcomingOrder(): Promise<Result<ScheduleOrder | null>>;
  getUpcomingOrders(count: number): Promise<Result<ScheduleOrder[]>>;
  getAdjustments(): Promise<Result<ScheduleAdjustment[]>>;
  skipOrder(params: {
    subscriptionId: string;
    cycle?: number;
    date?: string;
  }): Promise<Result>;
  changeDateAdjustment(params: {
    subscriptionId: string;
    newDate: string;
    currentDate?: string;
  }): Promise<Result>;
  removeAdjustment(params: { adjustmentId: string }): Promise<Result>;
};

Reactive state

  • upcoming — list of upcoming orders. Multiple blocks can read it without duplicating fetches.
  • hasPendingBilling — mirrors the subscription service signal so schedule-modifying blocks can disable controls during billing.

Example: skip next order

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

defineBlock("SkipNext", {
  group: "theme",
  schema: { /* ... */ } as const,
  initialValue: () => ({ props: {} }),
  renderer(block) {
    const el = document.createElement("button");
    const schedules = injectContext(el, SchedulesServiceContext);

    el.addEventListener("click", async () => {
      const next = schedules.upcoming.value[0];
      if (!next) return;
      await schedules.skipOrder({
        subscriptionId: next.subscriptionId,
        date: next.deliveryDate,
      });
    });

    effect(() => {
      const next = schedules.upcoming.value[0];
      const pending = schedules.hasPendingBilling.value;
      untracked(() => {
        el.textContent = next ? `Skip ${next.deliveryDate}` : "No upcoming order";
        el.disabled = pending || !next;
      });
    });

    return el;
  },
});

Adjustments

ScheduleAdjustment represents pending changes to the calendar — a skipped cycle or a moved date. Read them with getAdjustments() to show “your next change” affordances and remove them with removeAdjustment().