@voyantjs/products-react 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @voyantjs/products-react
2
+
3
+ React runtime package for Voyant products. Provides the shared products provider, typed fetch client, query keys, and TanStack Query hooks that power product-focused frontend experiences.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @voyantjs/products-react @voyantjs/products @tanstack/react-query react react-dom zod
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```tsx
14
+ import { VoyantProductsProvider, useProducts } from "@voyantjs/products-react"
15
+
16
+ function App() {
17
+ return (
18
+ <VoyantProductsProvider baseUrl="/api">
19
+ <ProductsList />
20
+ </VoyantProductsProvider>
21
+ )
22
+ }
23
+
24
+ function ProductsList() {
25
+ const { data } = useProducts()
26
+ return <>{data?.data.map((product) => <div key={product.id}>{product.name}</div>)}</>
27
+ }
28
+ ```
29
+
30
+ ## Relationship To The Registry
31
+
32
+ `@voyantjs/products-react` is the runtime layer. Installable product UI blocks should come from the Voyant shadcn registry and depend on this package for hooks, client state, and provider wiring.
33
+
34
+ ## License
35
+
36
+ FSL-1.1-Apache-2.0
@@ -0,0 +1,14 @@
1
+ import type { z } from "zod";
2
+ export type VoyantFetcher = (url: string, init?: RequestInit) => Promise<Response>;
3
+ export declare const defaultFetcher: VoyantFetcher;
4
+ export declare class VoyantApiError extends Error {
5
+ readonly status: number;
6
+ readonly body: unknown;
7
+ constructor(message: string, status: number, body: unknown);
8
+ }
9
+ export interface FetchWithValidationOptions {
10
+ baseUrl: string;
11
+ fetcher: VoyantFetcher;
12
+ }
13
+ export declare function fetchWithValidation<TOut>(path: string, schema: z.ZodType<TOut>, options: FetchWithValidationOptions, init?: RequestInit): Promise<TOut>;
14
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAE5B,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAElF,eAAO,MAAM,cAAc,EAAE,aACoB,CAAA;AAEjD,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;gBAEV,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;CAM3D;AAaD,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,aAAa,CAAA;CACvB;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAC5C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EACvB,OAAO,EAAE,0BAA0B,EACnC,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAgCf"}
package/dist/client.js ADDED
@@ -0,0 +1,59 @@
1
+ export const defaultFetcher = (url, init) => fetch(url, { credentials: "include", ...init });
2
+ export class VoyantApiError extends Error {
3
+ status;
4
+ body;
5
+ constructor(message, status, body) {
6
+ super(message);
7
+ this.name = "VoyantApiError";
8
+ this.status = status;
9
+ this.body = body;
10
+ }
11
+ }
12
+ function extractErrorMessage(status, statusText, body) {
13
+ if (typeof body === "object" && body !== null && "error" in body) {
14
+ const err = body.error;
15
+ if (typeof err === "string")
16
+ return err;
17
+ if (typeof err === "object" && err !== null && "message" in err) {
18
+ return String(err.message);
19
+ }
20
+ }
21
+ return `Voyant API error: ${status} ${statusText}`;
22
+ }
23
+ export async function fetchWithValidation(path, schema, options, init) {
24
+ const url = joinUrl(options.baseUrl, path);
25
+ const headers = new Headers(init?.headers);
26
+ if (init?.body !== undefined && !headers.has("Content-Type")) {
27
+ headers.set("Content-Type", "application/json");
28
+ }
29
+ const response = await options.fetcher(url, { ...init, headers });
30
+ if (!response.ok) {
31
+ const body = await safeJson(response);
32
+ throw new VoyantApiError(extractErrorMessage(response.status, response.statusText, body), response.status, body);
33
+ }
34
+ if (response.status === 204) {
35
+ return schema.parse(undefined);
36
+ }
37
+ const body = await safeJson(response);
38
+ const parsed = schema.safeParse(body);
39
+ if (!parsed.success) {
40
+ throw new VoyantApiError(`Voyant API response failed validation: ${parsed.error.message}`, response.status, body);
41
+ }
42
+ return parsed.data;
43
+ }
44
+ async function safeJson(response) {
45
+ const text = await response.text();
46
+ if (!text)
47
+ return undefined;
48
+ try {
49
+ return JSON.parse(text);
50
+ }
51
+ catch {
52
+ return text;
53
+ }
54
+ }
55
+ function joinUrl(baseUrl, path) {
56
+ const trimmedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
57
+ const trimmedPath = path.startsWith("/") ? path : `/${path}`;
58
+ return `${trimmedBase}${trimmedPath}`;
59
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./use-product.js";
2
+ export * from "./use-product-mutation.js";
3
+ export * from "./use-product-types.js";
4
+ export * from "./use-products.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,mBAAmB,CAAA"}
@@ -0,0 +1,4 @@
1
+ export * from "./use-product.js";
2
+ export * from "./use-product-mutation.js";
3
+ export * from "./use-product-types.js";
4
+ export * from "./use-products.js";
@@ -0,0 +1,79 @@
1
+ export interface CreateProductInput {
2
+ name: string;
3
+ status?: "draft" | "active" | "archived";
4
+ description?: string | null;
5
+ bookingMode?: "date" | "date_time" | "open" | "stay" | "transfer" | "itinerary" | "other";
6
+ capacityMode?: "free_sale" | "limited" | "on_request";
7
+ timezone?: string | null;
8
+ visibility?: "public" | "private" | "hidden";
9
+ activated?: boolean;
10
+ reservationTimeoutMinutes?: number | null;
11
+ sellCurrency: string;
12
+ sellAmountCents?: number | null;
13
+ costAmountCents?: number | null;
14
+ marginPercent?: number | null;
15
+ facilityId?: string | null;
16
+ productTypeId?: string | null;
17
+ startDate?: string | null;
18
+ endDate?: string | null;
19
+ pax?: number | null;
20
+ tags?: string[];
21
+ }
22
+ export type UpdateProductInput = Partial<CreateProductInput>;
23
+ export declare function useProductMutation(): {
24
+ create: import("@tanstack/react-query").UseMutationResult<{
25
+ id: string;
26
+ name: string;
27
+ status: "draft" | "active" | "archived";
28
+ description: string | null;
29
+ bookingMode: "date" | "date_time" | "open" | "stay" | "transfer" | "itinerary" | "other";
30
+ capacityMode: "free_sale" | "limited" | "on_request";
31
+ timezone: string | null;
32
+ visibility: "public" | "private" | "hidden";
33
+ activated: boolean;
34
+ reservationTimeoutMinutes: number | null;
35
+ sellCurrency: string;
36
+ sellAmountCents: number | null;
37
+ costAmountCents: number | null;
38
+ marginPercent: number | null;
39
+ facilityId: string | null;
40
+ startDate: string | null;
41
+ endDate: string | null;
42
+ pax: number | null;
43
+ productTypeId: string | null;
44
+ tags: string[];
45
+ createdAt: string;
46
+ updatedAt: string;
47
+ }, Error, CreateProductInput, unknown>;
48
+ update: import("@tanstack/react-query").UseMutationResult<{
49
+ id: string;
50
+ name: string;
51
+ status: "draft" | "active" | "archived";
52
+ description: string | null;
53
+ bookingMode: "date" | "date_time" | "open" | "stay" | "transfer" | "itinerary" | "other";
54
+ capacityMode: "free_sale" | "limited" | "on_request";
55
+ timezone: string | null;
56
+ visibility: "public" | "private" | "hidden";
57
+ activated: boolean;
58
+ reservationTimeoutMinutes: number | null;
59
+ sellCurrency: string;
60
+ sellAmountCents: number | null;
61
+ costAmountCents: number | null;
62
+ marginPercent: number | null;
63
+ facilityId: string | null;
64
+ startDate: string | null;
65
+ endDate: string | null;
66
+ pax: number | null;
67
+ productTypeId: string | null;
68
+ tags: string[];
69
+ createdAt: string;
70
+ updatedAt: string;
71
+ }, Error, {
72
+ id: string;
73
+ input: UpdateProductInput;
74
+ }, unknown>;
75
+ remove: import("@tanstack/react-query").UseMutationResult<{
76
+ success: boolean;
77
+ }, Error, string, unknown>;
78
+ };
79
+ //# sourceMappingURL=use-product-mutation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-product-mutation.d.ts","sourceRoot":"","sources":["../../src/hooks/use-product-mutation.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAA;IACxC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,CAAA;IACzF,YAAY,CAAC,EAAE,WAAW,GAAG,SAAS,GAAG,YAAY,CAAA;IACrD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;IAC5C,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,yBAAyB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzC,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;AAI5D,wBAAgB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAkBU,MAAM;eAAS,kBAAkB;;;;;EA+B5E"}
@@ -0,0 +1,44 @@
1
+ "use client";
2
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
3
+ import { z } from "zod";
4
+ import { fetchWithValidation } from "../client.js";
5
+ import { useVoyantProductsContext } from "../provider.js";
6
+ import { productsQueryKeys } from "../query-keys.js";
7
+ import { productSingleResponse } from "../schemas.js";
8
+ const deleteResponseSchema = z.object({ success: z.boolean() });
9
+ export function useProductMutation() {
10
+ const { baseUrl, fetcher } = useVoyantProductsContext();
11
+ const queryClient = useQueryClient();
12
+ const create = useMutation({
13
+ mutationFn: async (input) => {
14
+ const { data } = await fetchWithValidation("/v1/products", productSingleResponse, {
15
+ baseUrl,
16
+ fetcher,
17
+ }, { method: "POST", body: JSON.stringify(input) });
18
+ return data;
19
+ },
20
+ onSuccess: () => {
21
+ void queryClient.invalidateQueries({ queryKey: productsQueryKeys.products() });
22
+ },
23
+ });
24
+ const update = useMutation({
25
+ mutationFn: async ({ id, input }) => {
26
+ const { data } = await fetchWithValidation(`/v1/products/${id}`, productSingleResponse, { baseUrl, fetcher }, { method: "PATCH", body: JSON.stringify(input) });
27
+ return data;
28
+ },
29
+ onSuccess: (data) => {
30
+ void queryClient.invalidateQueries({ queryKey: productsQueryKeys.products() });
31
+ queryClient.setQueryData(productsQueryKeys.product(data.id), data);
32
+ },
33
+ });
34
+ const remove = useMutation({
35
+ mutationFn: async (id) => {
36
+ return fetchWithValidation(`/v1/products/${id}`, deleteResponseSchema, { baseUrl, fetcher }, { method: "DELETE" });
37
+ },
38
+ onSuccess: (_data, id) => {
39
+ void queryClient.invalidateQueries({ queryKey: productsQueryKeys.products() });
40
+ queryClient.removeQueries({ queryKey: productsQueryKeys.product(id) });
41
+ },
42
+ });
43
+ return { create, update, remove };
44
+ }
@@ -0,0 +1,21 @@
1
+ import { type ProductTypesListFilters } from "../query-keys.js";
2
+ export interface UseProductTypesOptions extends ProductTypesListFilters {
3
+ enabled?: boolean;
4
+ }
5
+ export declare function useProductTypes(options?: UseProductTypesOptions): import("@tanstack/react-query").UseQueryResult<{
6
+ data: {
7
+ id: string;
8
+ name: string;
9
+ code: string;
10
+ description: string | null;
11
+ sortOrder: number;
12
+ active: boolean;
13
+ createdAt: string;
14
+ updatedAt: string;
15
+ metadata?: Record<string, unknown> | null | undefined;
16
+ }[];
17
+ total: number;
18
+ limit: number;
19
+ offset: number;
20
+ }, Error>;
21
+ //# sourceMappingURL=use-product-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-product-types.d.ts","sourceRoot":"","sources":["../../src/hooks/use-product-types.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,uBAAuB,EAAqB,MAAM,kBAAkB,CAAA;AAGlF,MAAM,WAAW,sBAAuB,SAAQ,uBAAuB;IACrE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B;;;;;;;;;;;;;;;UAqBnE"}
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { fetchWithValidation } from "../client.js";
4
+ import { useVoyantProductsContext } from "../provider.js";
5
+ import { productsQueryKeys } from "../query-keys.js";
6
+ import { productTypeListResponse } from "../schemas.js";
7
+ export function useProductTypes(options = {}) {
8
+ const { baseUrl, fetcher } = useVoyantProductsContext();
9
+ const { enabled = true, ...filters } = options;
10
+ return useQuery({
11
+ queryKey: productsQueryKeys.productTypesList(filters),
12
+ queryFn: () => {
13
+ const params = new URLSearchParams();
14
+ if (filters.active !== undefined)
15
+ params.set("active", String(filters.active));
16
+ if (filters.search)
17
+ params.set("search", filters.search);
18
+ if (filters.limit !== undefined)
19
+ params.set("limit", String(filters.limit));
20
+ if (filters.offset !== undefined)
21
+ params.set("offset", String(filters.offset));
22
+ const qs = params.toString();
23
+ return fetchWithValidation(`/v1/products/product-types${qs ? `?${qs}` : ""}`, productTypeListResponse, { baseUrl, fetcher });
24
+ },
25
+ enabled,
26
+ });
27
+ }
@@ -0,0 +1,28 @@
1
+ export interface UseProductOptions {
2
+ enabled?: boolean;
3
+ }
4
+ export declare function useProduct(id: string | undefined, options?: UseProductOptions): import("@tanstack/react-query").UseQueryResult<{
5
+ id: string;
6
+ name: string;
7
+ status: "draft" | "active" | "archived";
8
+ description: string | null;
9
+ bookingMode: "date" | "date_time" | "open" | "stay" | "transfer" | "itinerary" | "other";
10
+ capacityMode: "free_sale" | "limited" | "on_request";
11
+ timezone: string | null;
12
+ visibility: "public" | "private" | "hidden";
13
+ activated: boolean;
14
+ reservationTimeoutMinutes: number | null;
15
+ sellCurrency: string;
16
+ sellAmountCents: number | null;
17
+ costAmountCents: number | null;
18
+ marginPercent: number | null;
19
+ facilityId: string | null;
20
+ startDate: string | null;
21
+ endDate: string | null;
22
+ pax: number | null;
23
+ productTypeId: string | null;
24
+ tags: string[];
25
+ createdAt: string;
26
+ updatedAt: string;
27
+ }, Error>;
28
+ //# sourceMappingURL=use-product.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-product.d.ts","sourceRoot":"","sources":["../../src/hooks/use-product.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,GAAE,iBAAsB;;;;;;;;;;;;;;;;;;;;;;;UAgBjF"}
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { fetchWithValidation } from "../client.js";
4
+ import { useVoyantProductsContext } from "../provider.js";
5
+ import { productsQueryKeys } from "../query-keys.js";
6
+ import { productSingleResponse } from "../schemas.js";
7
+ export function useProduct(id, options = {}) {
8
+ const { baseUrl, fetcher } = useVoyantProductsContext();
9
+ const { enabled = true } = options;
10
+ return useQuery({
11
+ queryKey: productsQueryKeys.product(id ?? ""),
12
+ queryFn: async () => {
13
+ if (!id)
14
+ throw new Error("useProduct requires an id");
15
+ const { data } = await fetchWithValidation(`/v1/products/${id}`, productSingleResponse, {
16
+ baseUrl,
17
+ fetcher,
18
+ });
19
+ return data;
20
+ },
21
+ enabled: enabled && Boolean(id),
22
+ });
23
+ }
@@ -0,0 +1,34 @@
1
+ import { type ProductsListFilters } from "../query-keys.js";
2
+ export interface UseProductsOptions extends ProductsListFilters {
3
+ enabled?: boolean;
4
+ }
5
+ export declare function useProducts(options?: UseProductsOptions): import("@tanstack/react-query").UseQueryResult<{
6
+ data: {
7
+ id: string;
8
+ name: string;
9
+ status: "draft" | "active" | "archived";
10
+ description: string | null;
11
+ bookingMode: "date" | "date_time" | "open" | "stay" | "transfer" | "itinerary" | "other";
12
+ capacityMode: "free_sale" | "limited" | "on_request";
13
+ timezone: string | null;
14
+ visibility: "public" | "private" | "hidden";
15
+ activated: boolean;
16
+ reservationTimeoutMinutes: number | null;
17
+ sellCurrency: string;
18
+ sellAmountCents: number | null;
19
+ costAmountCents: number | null;
20
+ marginPercent: number | null;
21
+ facilityId: string | null;
22
+ startDate: string | null;
23
+ endDate: string | null;
24
+ pax: number | null;
25
+ productTypeId: string | null;
26
+ tags: string[];
27
+ createdAt: string;
28
+ updatedAt: string;
29
+ }[];
30
+ total: number;
31
+ limit: number;
32
+ offset: number;
33
+ }, Error>;
34
+ //# sourceMappingURL=use-products.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-products.d.ts","sourceRoot":"","sources":["../../src/hooks/use-products.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,mBAAmB,EAAqB,MAAM,kBAAkB,CAAA;AAG9E,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAwB3D"}
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { fetchWithValidation } from "../client.js";
4
+ import { useVoyantProductsContext } from "../provider.js";
5
+ import { productsQueryKeys } from "../query-keys.js";
6
+ import { productListResponse } from "../schemas.js";
7
+ export function useProducts(options = {}) {
8
+ const { baseUrl, fetcher } = useVoyantProductsContext();
9
+ const { enabled = true, ...filters } = options;
10
+ return useQuery({
11
+ queryKey: productsQueryKeys.productsList(filters),
12
+ queryFn: () => {
13
+ const params = new URLSearchParams();
14
+ if (filters.status)
15
+ params.set("status", filters.status);
16
+ if (filters.bookingMode)
17
+ params.set("bookingMode", filters.bookingMode);
18
+ if (filters.visibility)
19
+ params.set("visibility", filters.visibility);
20
+ if (filters.activated !== undefined)
21
+ params.set("activated", String(filters.activated));
22
+ if (filters.facilityId)
23
+ params.set("facilityId", filters.facilityId);
24
+ if (filters.search)
25
+ params.set("search", filters.search);
26
+ if (filters.limit !== undefined)
27
+ params.set("limit", String(filters.limit));
28
+ if (filters.offset !== undefined)
29
+ params.set("offset", String(filters.offset));
30
+ const qs = params.toString();
31
+ return fetchWithValidation(`/v1/products${qs ? `?${qs}` : ""}`, productListResponse, {
32
+ baseUrl,
33
+ fetcher,
34
+ });
35
+ },
36
+ enabled,
37
+ });
38
+ }
@@ -0,0 +1,6 @@
1
+ export { fetchWithValidation, defaultFetcher, VoyantApiError, type VoyantFetcher, } from "./client.js";
2
+ export { VoyantProductsProvider, useVoyantProductsContext, type VoyantProductsContextValue, type VoyantProductsProviderProps, } from "./provider.js";
3
+ export { productsQueryKeys } from "./query-keys.js";
4
+ export * from "./schemas.js";
5
+ export * from "./hooks/index.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,KAAK,aAAa,GACnB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,GACjC,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACnD,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { fetchWithValidation, defaultFetcher, VoyantApiError, } from "./client.js";
2
+ export { VoyantProductsProvider, useVoyantProductsContext, } from "./provider.js";
3
+ export { productsQueryKeys } from "./query-keys.js";
4
+ export * from "./schemas.js";
5
+ export * from "./hooks/index.js";
@@ -0,0 +1,14 @@
1
+ import { type ReactNode } from "react";
2
+ import { type VoyantFetcher } from "./client.js";
3
+ export interface VoyantProductsContextValue {
4
+ baseUrl: string;
5
+ fetcher: VoyantFetcher;
6
+ }
7
+ export interface VoyantProductsProviderProps {
8
+ baseUrl: string;
9
+ fetcher?: VoyantFetcher;
10
+ children: ReactNode;
11
+ }
12
+ export declare function VoyantProductsProvider({ baseUrl, fetcher, children, }: VoyantProductsProviderProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function useVoyantProductsContext(): VoyantProductsContextValue;
14
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAA;AAE1E,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhE,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,aAAa,CAAA;CACvB;AAID,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,OAAO,EACP,OAAO,EACP,QAAQ,GACT,EAAE,2BAA2B,2CAM7B;AAED,wBAAgB,wBAAwB,IAAI,0BAA0B,CAQrE"}
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext, useMemo } from "react";
4
+ import { defaultFetcher } from "./client.js";
5
+ const VoyantProductsContext = createContext(null);
6
+ export function VoyantProductsProvider({ baseUrl, fetcher, children, }) {
7
+ const value = useMemo(() => ({ baseUrl, fetcher: fetcher ?? defaultFetcher }), [baseUrl, fetcher]);
8
+ return _jsx(VoyantProductsContext.Provider, { value: value, children: children });
9
+ }
10
+ export function useVoyantProductsContext() {
11
+ const context = useContext(VoyantProductsContext);
12
+ if (!context) {
13
+ throw new Error('useVoyantProductsContext must be used inside <VoyantProductsProvider>. Wrap your app with <VoyantProductsProvider baseUrl="/api" />.');
14
+ }
15
+ return context;
16
+ }
@@ -0,0 +1,26 @@
1
+ export interface ProductsListFilters {
2
+ status?: string | undefined;
3
+ bookingMode?: string | undefined;
4
+ visibility?: string | undefined;
5
+ activated?: boolean | undefined;
6
+ facilityId?: string | undefined;
7
+ search?: string | undefined;
8
+ limit?: number | undefined;
9
+ offset?: number | undefined;
10
+ }
11
+ export interface ProductTypesListFilters {
12
+ active?: boolean | undefined;
13
+ search?: string | undefined;
14
+ limit?: number | undefined;
15
+ offset?: number | undefined;
16
+ }
17
+ export declare const productsQueryKeys: {
18
+ readonly all: readonly ["voyant", "products"];
19
+ readonly products: () => readonly ["voyant", "products", "products"];
20
+ readonly productsList: (filters: ProductsListFilters) => readonly ["voyant", "products", "products", "list", ProductsListFilters];
21
+ readonly product: (id: string) => readonly ["voyant", "products", "products", "detail", string];
22
+ readonly productTypes: () => readonly ["voyant", "products", "product-types"];
23
+ readonly productTypesList: (filters: ProductTypesListFilters) => readonly ["voyant", "products", "product-types", "list", ProductTypesListFilters];
24
+ readonly productType: (id: string) => readonly ["voyant", "products", "product-types", "detail", string];
25
+ };
26
+ //# sourceMappingURL=query-keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-keys.d.ts","sourceRoot":"","sources":["../src/query-keys.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5B;AAED,eAAO,MAAM,iBAAiB;;;qCAIJ,mBAAmB;2BAE7B,MAAM;;yCAGQ,uBAAuB;+BAEjC,MAAM;CAChB,CAAA"}
@@ -0,0 +1,9 @@
1
+ export const productsQueryKeys = {
2
+ all: ["voyant", "products"],
3
+ products: () => [...productsQueryKeys.all, "products"],
4
+ productsList: (filters) => [...productsQueryKeys.products(), "list", filters],
5
+ product: (id) => [...productsQueryKeys.products(), "detail", id],
6
+ productTypes: () => [...productsQueryKeys.all, "product-types"],
7
+ productTypesList: (filters) => [...productsQueryKeys.productTypes(), "list", filters],
8
+ productType: (id) => [...productsQueryKeys.productTypes(), "detail", id],
9
+ };
@@ -0,0 +1,195 @@
1
+ import { z } from "zod";
2
+ export declare const paginatedEnvelope: <T extends z.ZodTypeAny>(item: T) => z.ZodObject<{
3
+ data: z.ZodArray<T>;
4
+ total: z.ZodNumber;
5
+ limit: z.ZodNumber;
6
+ offset: z.ZodNumber;
7
+ }, z.core.$strip>;
8
+ export declare const singleEnvelope: <T extends z.ZodTypeAny>(item: T) => z.ZodObject<{
9
+ data: T;
10
+ }, z.core.$strip>;
11
+ export declare const successEnvelope: z.ZodObject<{
12
+ success: z.ZodBoolean;
13
+ }, z.core.$strip>;
14
+ export declare const productRecordSchema: z.ZodObject<{
15
+ id: z.ZodString;
16
+ name: z.ZodString;
17
+ status: z.ZodEnum<{
18
+ draft: "draft";
19
+ active: "active";
20
+ archived: "archived";
21
+ }>;
22
+ description: z.ZodNullable<z.ZodString>;
23
+ bookingMode: z.ZodEnum<{
24
+ date: "date";
25
+ date_time: "date_time";
26
+ open: "open";
27
+ stay: "stay";
28
+ transfer: "transfer";
29
+ itinerary: "itinerary";
30
+ other: "other";
31
+ }>;
32
+ capacityMode: z.ZodEnum<{
33
+ free_sale: "free_sale";
34
+ limited: "limited";
35
+ on_request: "on_request";
36
+ }>;
37
+ timezone: z.ZodNullable<z.ZodString>;
38
+ visibility: z.ZodEnum<{
39
+ public: "public";
40
+ private: "private";
41
+ hidden: "hidden";
42
+ }>;
43
+ activated: z.ZodBoolean;
44
+ reservationTimeoutMinutes: z.ZodNullable<z.ZodNumber>;
45
+ sellCurrency: z.ZodString;
46
+ sellAmountCents: z.ZodNullable<z.ZodNumber>;
47
+ costAmountCents: z.ZodNullable<z.ZodNumber>;
48
+ marginPercent: z.ZodNullable<z.ZodNumber>;
49
+ facilityId: z.ZodNullable<z.ZodString>;
50
+ startDate: z.ZodNullable<z.ZodString>;
51
+ endDate: z.ZodNullable<z.ZodString>;
52
+ pax: z.ZodNullable<z.ZodNumber>;
53
+ productTypeId: z.ZodNullable<z.ZodString>;
54
+ tags: z.ZodArray<z.ZodString>;
55
+ createdAt: z.ZodString;
56
+ updatedAt: z.ZodString;
57
+ }, z.core.$strip>;
58
+ export type ProductRecord = z.infer<typeof productRecordSchema>;
59
+ export declare const productTypeRecordSchema: z.ZodObject<{
60
+ id: z.ZodString;
61
+ name: z.ZodString;
62
+ code: z.ZodString;
63
+ description: z.ZodNullable<z.ZodString>;
64
+ sortOrder: z.ZodNumber;
65
+ active: z.ZodBoolean;
66
+ metadata: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
67
+ createdAt: z.ZodString;
68
+ updatedAt: z.ZodString;
69
+ }, z.core.$strip>;
70
+ export type ProductTypeRecord = z.infer<typeof productTypeRecordSchema>;
71
+ export declare const productListResponse: z.ZodObject<{
72
+ data: z.ZodArray<z.ZodObject<{
73
+ id: z.ZodString;
74
+ name: z.ZodString;
75
+ status: z.ZodEnum<{
76
+ draft: "draft";
77
+ active: "active";
78
+ archived: "archived";
79
+ }>;
80
+ description: z.ZodNullable<z.ZodString>;
81
+ bookingMode: z.ZodEnum<{
82
+ date: "date";
83
+ date_time: "date_time";
84
+ open: "open";
85
+ stay: "stay";
86
+ transfer: "transfer";
87
+ itinerary: "itinerary";
88
+ other: "other";
89
+ }>;
90
+ capacityMode: z.ZodEnum<{
91
+ free_sale: "free_sale";
92
+ limited: "limited";
93
+ on_request: "on_request";
94
+ }>;
95
+ timezone: z.ZodNullable<z.ZodString>;
96
+ visibility: z.ZodEnum<{
97
+ public: "public";
98
+ private: "private";
99
+ hidden: "hidden";
100
+ }>;
101
+ activated: z.ZodBoolean;
102
+ reservationTimeoutMinutes: z.ZodNullable<z.ZodNumber>;
103
+ sellCurrency: z.ZodString;
104
+ sellAmountCents: z.ZodNullable<z.ZodNumber>;
105
+ costAmountCents: z.ZodNullable<z.ZodNumber>;
106
+ marginPercent: z.ZodNullable<z.ZodNumber>;
107
+ facilityId: z.ZodNullable<z.ZodString>;
108
+ startDate: z.ZodNullable<z.ZodString>;
109
+ endDate: z.ZodNullable<z.ZodString>;
110
+ pax: z.ZodNullable<z.ZodNumber>;
111
+ productTypeId: z.ZodNullable<z.ZodString>;
112
+ tags: z.ZodArray<z.ZodString>;
113
+ createdAt: z.ZodString;
114
+ updatedAt: z.ZodString;
115
+ }, z.core.$strip>>;
116
+ total: z.ZodNumber;
117
+ limit: z.ZodNumber;
118
+ offset: z.ZodNumber;
119
+ }, z.core.$strip>;
120
+ export declare const productSingleResponse: z.ZodObject<{
121
+ data: z.ZodObject<{
122
+ id: z.ZodString;
123
+ name: z.ZodString;
124
+ status: z.ZodEnum<{
125
+ draft: "draft";
126
+ active: "active";
127
+ archived: "archived";
128
+ }>;
129
+ description: z.ZodNullable<z.ZodString>;
130
+ bookingMode: z.ZodEnum<{
131
+ date: "date";
132
+ date_time: "date_time";
133
+ open: "open";
134
+ stay: "stay";
135
+ transfer: "transfer";
136
+ itinerary: "itinerary";
137
+ other: "other";
138
+ }>;
139
+ capacityMode: z.ZodEnum<{
140
+ free_sale: "free_sale";
141
+ limited: "limited";
142
+ on_request: "on_request";
143
+ }>;
144
+ timezone: z.ZodNullable<z.ZodString>;
145
+ visibility: z.ZodEnum<{
146
+ public: "public";
147
+ private: "private";
148
+ hidden: "hidden";
149
+ }>;
150
+ activated: z.ZodBoolean;
151
+ reservationTimeoutMinutes: z.ZodNullable<z.ZodNumber>;
152
+ sellCurrency: z.ZodString;
153
+ sellAmountCents: z.ZodNullable<z.ZodNumber>;
154
+ costAmountCents: z.ZodNullable<z.ZodNumber>;
155
+ marginPercent: z.ZodNullable<z.ZodNumber>;
156
+ facilityId: z.ZodNullable<z.ZodString>;
157
+ startDate: z.ZodNullable<z.ZodString>;
158
+ endDate: z.ZodNullable<z.ZodString>;
159
+ pax: z.ZodNullable<z.ZodNumber>;
160
+ productTypeId: z.ZodNullable<z.ZodString>;
161
+ tags: z.ZodArray<z.ZodString>;
162
+ createdAt: z.ZodString;
163
+ updatedAt: z.ZodString;
164
+ }, z.core.$strip>;
165
+ }, z.core.$strip>;
166
+ export declare const productTypeListResponse: z.ZodObject<{
167
+ data: z.ZodArray<z.ZodObject<{
168
+ id: z.ZodString;
169
+ name: z.ZodString;
170
+ code: z.ZodString;
171
+ description: z.ZodNullable<z.ZodString>;
172
+ sortOrder: z.ZodNumber;
173
+ active: z.ZodBoolean;
174
+ metadata: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
175
+ createdAt: z.ZodString;
176
+ updatedAt: z.ZodString;
177
+ }, z.core.$strip>>;
178
+ total: z.ZodNumber;
179
+ limit: z.ZodNumber;
180
+ offset: z.ZodNumber;
181
+ }, z.core.$strip>;
182
+ export declare const productTypeSingleResponse: z.ZodObject<{
183
+ data: z.ZodObject<{
184
+ id: z.ZodString;
185
+ name: z.ZodString;
186
+ code: z.ZodString;
187
+ description: z.ZodNullable<z.ZodString>;
188
+ sortOrder: z.ZodNumber;
189
+ active: z.ZodBoolean;
190
+ metadata: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
191
+ createdAt: z.ZodString;
192
+ updatedAt: z.ZodString;
193
+ }, z.core.$strip>;
194
+ }, z.core.$strip>;
195
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,iBAAiB,GAAI,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC;;;;;iBAM7D,CAAA;AAEJ,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC;;iBAA6B,CAAA;AAE3F,eAAO,MAAM,eAAe;;iBAAqC,CAAA;AAEjE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuB9B,CAAA;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAE/D,eAAO,MAAM,uBAAuB;;;;;;;;;;iBAUlC,CAAA;AAEF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAA;AAEvE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAyC,CAAA;AACzE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsC,CAAA;AACxE,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;iBAA6C,CAAA;AACjF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;iBAA0C,CAAA"}
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+ export const paginatedEnvelope = (item) => z.object({
3
+ data: z.array(item),
4
+ total: z.number().int(),
5
+ limit: z.number().int(),
6
+ offset: z.number().int(),
7
+ });
8
+ export const singleEnvelope = (item) => z.object({ data: item });
9
+ export const successEnvelope = z.object({ success: z.boolean() });
10
+ export const productRecordSchema = z.object({
11
+ id: z.string(),
12
+ name: z.string(),
13
+ status: z.enum(["draft", "active", "archived"]),
14
+ description: z.string().nullable(),
15
+ bookingMode: z.enum(["date", "date_time", "open", "stay", "transfer", "itinerary", "other"]),
16
+ capacityMode: z.enum(["free_sale", "limited", "on_request"]),
17
+ timezone: z.string().nullable(),
18
+ visibility: z.enum(["public", "private", "hidden"]),
19
+ activated: z.boolean(),
20
+ reservationTimeoutMinutes: z.number().int().nullable(),
21
+ sellCurrency: z.string(),
22
+ sellAmountCents: z.number().int().nullable(),
23
+ costAmountCents: z.number().int().nullable(),
24
+ marginPercent: z.number().int().nullable(),
25
+ facilityId: z.string().nullable(),
26
+ startDate: z.string().nullable(),
27
+ endDate: z.string().nullable(),
28
+ pax: z.number().int().nullable(),
29
+ productTypeId: z.string().nullable(),
30
+ tags: z.array(z.string()),
31
+ createdAt: z.string(),
32
+ updatedAt: z.string(),
33
+ });
34
+ export const productTypeRecordSchema = z.object({
35
+ id: z.string(),
36
+ name: z.string(),
37
+ code: z.string(),
38
+ description: z.string().nullable(),
39
+ sortOrder: z.number().int(),
40
+ active: z.boolean(),
41
+ metadata: z.record(z.string(), z.unknown()).nullable().optional(),
42
+ createdAt: z.string(),
43
+ updatedAt: z.string(),
44
+ });
45
+ export const productListResponse = paginatedEnvelope(productRecordSchema);
46
+ export const productSingleResponse = singleEnvelope(productRecordSchema);
47
+ export const productTypeListResponse = paginatedEnvelope(productTypeRecordSchema);
48
+ export const productTypeSingleResponse = singleEnvelope(productTypeRecordSchema);
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@voyantjs/products-react",
3
+ "version": "0.1.1",
4
+ "license": "FSL-1.1-Apache-2.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/voyantjs/voyant.git",
8
+ "directory": "packages/products-react"
9
+ },
10
+ "type": "module",
11
+ "exports": {
12
+ ".": "./src/index.ts",
13
+ "./provider": "./src/provider.tsx",
14
+ "./hooks": "./src/hooks/index.ts",
15
+ "./client": "./src/client.ts",
16
+ "./query-keys": "./src/query-keys.ts"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "clean": "rm -rf dist",
21
+ "prepack": "pnpm run build",
22
+ "typecheck": "tsc --noEmit",
23
+ "lint": "biome check src/",
24
+ "test": "vitest run --passWithNoTests"
25
+ },
26
+ "peerDependencies": {
27
+ "@voyantjs/products": "workspace:*",
28
+ "@tanstack/react-query": "^5.0.0",
29
+ "react": "^19.0.0",
30
+ "react-dom": "^19.0.0",
31
+ "zod": "^4.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@voyantjs/products": "workspace:*",
35
+ "@voyantjs/voyant-typescript-config": "workspace:*",
36
+ "@tanstack/react-query": "^5.96.2",
37
+ "@types/react": "^19.2.14",
38
+ "@types/react-dom": "^19.2.3",
39
+ "react": "^19.2.4",
40
+ "react-dom": "^19.2.4",
41
+ "typescript": "^6.0.2",
42
+ "vitest": "^4.1.2",
43
+ "zod": "^4.3.6"
44
+ },
45
+ "files": [
46
+ "dist"
47
+ ],
48
+ "publishConfig": {
49
+ "access": "public",
50
+ "exports": {
51
+ ".": {
52
+ "types": "./dist/index.d.ts",
53
+ "import": "./dist/index.js"
54
+ },
55
+ "./provider": {
56
+ "types": "./dist/provider.d.ts",
57
+ "import": "./dist/provider.js"
58
+ },
59
+ "./hooks": {
60
+ "types": "./dist/hooks/index.d.ts",
61
+ "import": "./dist/hooks/index.js"
62
+ },
63
+ "./client": {
64
+ "types": "./dist/client.d.ts",
65
+ "import": "./dist/client.js"
66
+ },
67
+ "./query-keys": {
68
+ "types": "./dist/query-keys.d.ts",
69
+ "import": "./dist/query-keys.js"
70
+ }
71
+ },
72
+ "main": "./dist/index.js",
73
+ "types": "./dist/index.d.ts"
74
+ }
75
+ }