@hartl-services/medusa-storefront 0.1.0 → 0.3.0
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 +24 -0
- package/dist/browser/actions.d.ts +6 -0
- package/dist/browser/cart-mutations.d.ts +2 -1
- package/dist/browser/cart-mutations.js +2 -2
- package/dist/browser/index.d.ts +5 -1
- package/dist/browser/query-keys.d.ts +1 -0
- package/dist/browser/query-keys.js +2 -0
- package/dist/browser/query-options.d.ts +4 -0
- package/dist/browser/query-options.js +6 -0
- package/dist/core/catalog/category-filter.d.ts +16 -0
- package/dist/core/catalog/category-filter.js +33 -0
- package/dist/core/catalog/partner-only.d.ts +18 -0
- package/dist/core/catalog/partner-only.js +31 -0
- package/dist/core/form-values.d.ts +96 -4
- package/dist/core/form-values.js +131 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/report-error.d.ts +1 -1
- package/dist/core/texts-de.d.ts +4 -0
- package/dist/core/texts-de.js +4 -0
- package/dist/react/use-account.d.ts +10 -0
- package/dist/react/use-account.js +10 -0
- package/dist/react/use-auth.d.ts +16 -1
- package/dist/react/use-auth.js +67 -0
- package/dist/react/use-cart.d.ts +26 -1
- package/dist/react/use-cart.js +50 -3
- package/dist/react/use-checkout-address-form.d.ts +12 -12
- package/dist/react/use-checkout.d.ts +8 -8
- package/dist/registry/account.json +2 -2
- package/dist/registry/affiliate.json +2 -2
- package/dist/registry/cart.json +1 -1
- package/dist/registry/checkout.json +1 -1
- package/dist/registry/form.json +1 -1
- package/dist/registry/registry.json +9 -9
- package/dist/registry/setup.json +3 -3
- package/dist/server/affiliate.d.ts +4 -1
- package/dist/server/affiliate.js +29 -0
- package/dist/server/catalog.d.ts +17 -3
- package/dist/server/catalog.js +54 -6
- package/dist/server/customer.d.ts +39 -2
- package/dist/server/customer.js +174 -4
- package/dist/server/index.d.ts +25 -1
- package/dist/server/index.js +4 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -73,6 +73,30 @@ The copied files belong to the shop: style them, rename them, change the
|
|
|
73
73
|
wording. Keep the commerce logic in the package — a fix there reaches the shop
|
|
74
74
|
with the next version; a fix only in a copied file does not.
|
|
75
75
|
|
|
76
|
+
## Optional actions
|
|
77
|
+
|
|
78
|
+
Some server functions are optional in `createStorefrontBrowser`'s `actions`,
|
|
79
|
+
so a shop wired before they existed keeps working. Add the `"use server"`
|
|
80
|
+
wrapper (as in the `setup` item's `lib/storefront/actions.ts`) to use them:
|
|
81
|
+
|
|
82
|
+
| Action | Since | Used by |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `registerCustomerWithProfile` | 0.2.0 | `usePasswordAuth().registerWithProfile` (name, phone, default address) |
|
|
85
|
+
| `getOrder` | 0.2.0 | `useAccountOrderDetail` (order lines and totals in the account) |
|
|
86
|
+
| `registerPartnerWithProfile` | 0.3.0 | `usePasswordAuth().registerPartner` (account, company address with optional UID, partner application) |
|
|
87
|
+
|
|
88
|
+
## Partner views (e.g. therapists)
|
|
89
|
+
|
|
90
|
+
A partner is an approved affiliate. For server rendering,
|
|
91
|
+
`storefront.affiliate.isActivePartner()` answers whether the logged-in
|
|
92
|
+
customer is one (`loadPartnerPortalState()` gives the full state, e.g.
|
|
93
|
+
`pending` for a submitted application). Both are cached per request and fail
|
|
94
|
+
closed: logged out, partner portal switched off or an unreachable backend all
|
|
95
|
+
count as "not a partner". A partner registration (`registerPartner`) creates
|
|
96
|
+
the account with the company address as default address and submits the
|
|
97
|
+
application from it; while the e-mail is unconfirmed, both wait until the
|
|
98
|
+
first login.
|
|
99
|
+
|
|
76
100
|
## Wiring rules
|
|
77
101
|
|
|
78
102
|
- Create `createStorefrontServer` and `createStorefrontBrowser` once each, at
|
|
@@ -15,6 +15,10 @@ export interface StorefrontActions {
|
|
|
15
15
|
loginCustomer: Server["customer"]["loginCustomer"];
|
|
16
16
|
checkCustomerVerification: Server["customer"]["checkCustomerVerification"];
|
|
17
17
|
registerCustomer: Server["customer"]["registerCustomer"];
|
|
18
|
+
/** Optional (since 0.2.0): registration with name, phone and address. */
|
|
19
|
+
registerCustomerWithProfile?: Server["customer"]["registerCustomerWithProfile"];
|
|
20
|
+
/** Optional (since 0.3.0): partner registration with company address and partner application. */
|
|
21
|
+
registerPartnerWithProfile?: Server["customer"]["registerPartnerWithProfile"];
|
|
18
22
|
requestLoginCode: Server["customer"]["requestLoginCode"];
|
|
19
23
|
loginWithCode: Server["customer"]["loginWithCode"];
|
|
20
24
|
requestCustomerPasswordReset: Server["customer"]["requestCustomerPasswordReset"];
|
|
@@ -44,6 +48,8 @@ export interface StorefrontActions {
|
|
|
44
48
|
revokeAffiliateCouponCode: Server["affiliate"]["revokeAffiliateCouponCode"];
|
|
45
49
|
createConsignmentReport: Server["affiliate"]["createConsignmentReport"];
|
|
46
50
|
listOrders: Server["orders"]["listOrders"];
|
|
51
|
+
/** Optional (since 0.2.0): one order with lines and totals (account order detail). */
|
|
52
|
+
getOrder?: Server["orders"]["getOrder"];
|
|
47
53
|
listDocuments: Server["documents"]["listDocuments"];
|
|
48
54
|
}
|
|
49
55
|
export {};
|
|
@@ -25,8 +25,9 @@ export declare function createCartMutations({ actions, createClient, referral, r
|
|
|
25
25
|
}>;
|
|
26
26
|
completeCart(cartId: string): Promise<HttpTypes.StoreCompleteCartResponse>;
|
|
27
27
|
/** Adds one unit of a variant, creating the cart first when there is none. */
|
|
28
|
-
addProductToCart({ activeCart, regionId, variantId }: {
|
|
28
|
+
addProductToCart({ activeCart, quantity, regionId, variantId }: {
|
|
29
29
|
activeCart: Pick<HttpTypes.StoreCart, "id"> | null;
|
|
30
|
+
quantity?: number;
|
|
30
31
|
regionId: string;
|
|
31
32
|
variantId: string;
|
|
32
33
|
}): Promise<Pick<HttpTypes.StoreCart, "id">>;
|
|
@@ -125,10 +125,10 @@ export function createCartMutations({ actions, createClient, referral, reportChe
|
|
|
125
125
|
return result;
|
|
126
126
|
},
|
|
127
127
|
/** Adds one unit of a variant, creating the cart first when there is none. */
|
|
128
|
-
async addProductToCart({ activeCart, regionId, variantId }) {
|
|
128
|
+
async addProductToCart({ activeCart, quantity = 1, regionId, variantId }) {
|
|
129
129
|
const cart = activeCart ?? (await createCart({ region_id: regionId }));
|
|
130
130
|
await addCartLineItem(cart.id, {
|
|
131
|
-
quantity
|
|
131
|
+
quantity,
|
|
132
132
|
variant_id: variantId,
|
|
133
133
|
});
|
|
134
134
|
return cart;
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -79,8 +79,9 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
|
|
|
79
79
|
ready: true;
|
|
80
80
|
}>;
|
|
81
81
|
completeCart(cartId: string): Promise<HttpTypes.StoreCompleteCartResponse>;
|
|
82
|
-
addProductToCart({ activeCart, regionId, variantId }: {
|
|
82
|
+
addProductToCart({ activeCart, quantity, regionId, variantId }: {
|
|
83
83
|
activeCart: Pick<HttpTypes.StoreCart, "id"> | null;
|
|
84
|
+
quantity?: number;
|
|
84
85
|
regionId: string;
|
|
85
86
|
variantId: string;
|
|
86
87
|
}): Promise<Pick<HttpTypes.StoreCart, "id">>;
|
|
@@ -253,6 +254,9 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
|
|
|
253
254
|
limit: number;
|
|
254
255
|
offset: number;
|
|
255
256
|
}], HttpTypes.StoreOrderListResponse, Error>;
|
|
257
|
+
orderDetail: (orderId: string) => import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<HttpTypes.StoreOrder | null, Error, HttpTypes.StoreOrder | null, readonly ["medusa", "orders", "detail", string]>, "queryFn"> & {
|
|
258
|
+
queryFn?: import("@tanstack/react-query").QueryFunction<HttpTypes.StoreOrder | null, readonly ["medusa", "orders", "detail", string], never> | undefined;
|
|
259
|
+
} & import("@tanstack/react-query").QueryKeyWithDataTag<readonly ["medusa", "orders", "detail", string], HttpTypes.StoreOrder | null, Error>;
|
|
256
260
|
customerPasswordPolicy: () => import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<{
|
|
257
261
|
min_length: number;
|
|
258
262
|
require_mixed_case: boolean;
|
|
@@ -11,6 +11,7 @@ export declare const medusaQueryKeys: {
|
|
|
11
11
|
limit: number;
|
|
12
12
|
offset: number;
|
|
13
13
|
}];
|
|
14
|
+
orderDetail: (orderId: string) => readonly ["medusa", "orders", "detail", string];
|
|
14
15
|
ordersAll: () => readonly ["medusa", "orders"];
|
|
15
16
|
paymentProviders: (regionId: string) => readonly ["medusa", "payment-providers", string];
|
|
16
17
|
pluginFeatures: () => readonly ["medusa", "plugin-features"];
|
|
@@ -4,6 +4,8 @@ export const medusaQueryKeys = {
|
|
|
4
4
|
customerPasswordPolicy: () => ["medusa", "customer-password-policy"],
|
|
5
5
|
documents: () => ["medusa", "documents"],
|
|
6
6
|
orders: (page) => ["medusa", "orders", page],
|
|
7
|
+
// Under ordersAll, so everything that resets the orders resets a detail too.
|
|
8
|
+
orderDetail: (orderId) => ["medusa", "orders", "detail", orderId],
|
|
7
9
|
ordersAll: () => ["medusa", "orders"],
|
|
8
10
|
paymentProviders: (regionId) => ["medusa", "payment-providers", regionId],
|
|
9
11
|
pluginFeatures: () => ["medusa", "plugin-features"],
|
|
@@ -111,6 +111,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
|
|
|
111
111
|
limit: number;
|
|
112
112
|
offset: number;
|
|
113
113
|
}], import("@medusajs/types").StoreOrderListResponse, Error>;
|
|
114
|
+
/** One order with its lines and totals; needs the optional `getOrder` action. */
|
|
115
|
+
orderDetail: (orderId: string) => import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<import("@medusajs/types").StoreOrder | null, Error, import("@medusajs/types").StoreOrder | null, readonly ["medusa", "orders", "detail", string]>, "queryFn"> & {
|
|
116
|
+
queryFn?: import("@tanstack/react-query").QueryFunction<import("@medusajs/types").StoreOrder | null, readonly ["medusa", "orders", "detail", string], never> | undefined;
|
|
117
|
+
} & import("@tanstack/react-query").QueryKeyWithDataTag<readonly ["medusa", "orders", "detail", string], import("@medusajs/types").StoreOrder | null, Error>;
|
|
114
118
|
customerPasswordPolicy: () => import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<{
|
|
115
119
|
min_length: number;
|
|
116
120
|
require_mixed_case: boolean;
|
|
@@ -78,6 +78,12 @@ export function createQueryOptions({ actions, affiliateApiPaths, cart, referral,
|
|
|
78
78
|
queryKey: medusaQueryKeys.orders({ limit, offset }),
|
|
79
79
|
queryFn: () => actions.listOrders({ limit, offset }),
|
|
80
80
|
}),
|
|
81
|
+
/** One order with its lines and totals; needs the optional `getOrder` action. */
|
|
82
|
+
orderDetail: (orderId) => queryOptions({
|
|
83
|
+
queryKey: medusaQueryKeys.orderDetail(orderId),
|
|
84
|
+
queryFn: async () => (actions.getOrder ? await actions.getOrder(orderId) : null),
|
|
85
|
+
enabled: Boolean(actions.getOrder) && Boolean(orderId),
|
|
86
|
+
}),
|
|
81
87
|
customerPasswordPolicy: () => queryOptions({
|
|
82
88
|
queryKey: medusaQueryKeys.customerPasswordPolicy(),
|
|
83
89
|
queryFn: () => actions.fetchCustomerPasswordPolicy(),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HttpTypes } from "@medusajs/types";
|
|
2
|
+
/** A category with the descendants Medusa returns for `include_descendants_tree`. */
|
|
3
|
+
export type CategoryTreeNode = Pick<HttpTypes.StoreProductCategory, "id" | "handle" | "name"> & {
|
|
4
|
+
category_children?: CategoryTreeNode[] | null;
|
|
5
|
+
};
|
|
6
|
+
/** The category and all its descendants: choosing a parent filter includes its subcategories. */
|
|
7
|
+
export declare function collectCategoryIds(category: CategoryTreeNode): string[];
|
|
8
|
+
/** The node with this handle anywhere in the trees, or null. */
|
|
9
|
+
export declare function findCategoryByHandle(trees: CategoryTreeNode[], handle: string): CategoryTreeNode | null;
|
|
10
|
+
/**
|
|
11
|
+
* Keeps the products that match every selected filter: a product matches one
|
|
12
|
+
* filter when it belongs to the chosen category or one of its descendants.
|
|
13
|
+
* Medusa's `category_id` is an OR list, so two filters at once (e.g. an
|
|
14
|
+
* application area AND an ingredient group) are intersected here.
|
|
15
|
+
*/
|
|
16
|
+
export declare function filterProductsByCategories<T extends Pick<HttpTypes.StoreProduct, "categories">>(products: T[], selected: CategoryTreeNode[]): T[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** The category and all its descendants: choosing a parent filter includes its subcategories. */
|
|
2
|
+
export function collectCategoryIds(category) {
|
|
3
|
+
return [category.id, ...(category.category_children ?? []).flatMap(collectCategoryIds)];
|
|
4
|
+
}
|
|
5
|
+
/** The node with this handle anywhere in the trees, or null. */
|
|
6
|
+
export function findCategoryByHandle(trees, handle) {
|
|
7
|
+
for (const node of trees) {
|
|
8
|
+
if (node.handle === handle) {
|
|
9
|
+
return node;
|
|
10
|
+
}
|
|
11
|
+
const match = findCategoryByHandle(node.category_children ?? [], handle);
|
|
12
|
+
if (match) {
|
|
13
|
+
return match;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Keeps the products that match every selected filter: a product matches one
|
|
20
|
+
* filter when it belongs to the chosen category or one of its descendants.
|
|
21
|
+
* Medusa's `category_id` is an OR list, so two filters at once (e.g. an
|
|
22
|
+
* application area AND an ingredient group) are intersected here.
|
|
23
|
+
*/
|
|
24
|
+
export function filterProductsByCategories(products, selected) {
|
|
25
|
+
if (selected.length === 0) {
|
|
26
|
+
return products;
|
|
27
|
+
}
|
|
28
|
+
const filters = selected.map((category) => new Set(collectCategoryIds(category)));
|
|
29
|
+
return products.filter((product) => {
|
|
30
|
+
const productCategoryIds = (product.categories ?? []).map((category) => category.id);
|
|
31
|
+
return filters.every((filter) => productCategoryIds.some((id) => filter.has(id)));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HttpTypes } from "@medusajs/types";
|
|
2
|
+
import type { CategoryTreeNode } from "./category-filter.js";
|
|
3
|
+
/** A category tree node with the metadata that carries the partner-only flag. */
|
|
4
|
+
export type PartnerCategoryTreeNode = CategoryTreeNode & {
|
|
5
|
+
category_children?: PartnerCategoryTreeNode[] | null;
|
|
6
|
+
metadata?: Record<string, unknown> | null;
|
|
7
|
+
};
|
|
8
|
+
/** Ids of every flagged category and all its descendants. */
|
|
9
|
+
export declare function collectPartnerOnlyCategoryIds(trees: PartnerCategoryTreeNode[]): Set<string>;
|
|
10
|
+
/** The trees without flagged categories (and their subcategories) - what a non-partner sees. */
|
|
11
|
+
export declare function withoutPartnerOnlyCategories<T extends PartnerCategoryTreeNode>(trees: T[]): T[];
|
|
12
|
+
/**
|
|
13
|
+
* A product only active partners may see and order: tagged
|
|
14
|
+
* `PARTNER_ONLY_PRODUCT_TAG` or in a partner-only category (see
|
|
15
|
+
* `collectPartnerOnlyCategoryIds`). The backend enforces the same rule when
|
|
16
|
+
* the cart is completed.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isPartnerOnlyProduct(product: Pick<HttpTypes.StoreProduct, "categories" | "tags">, partnerOnlyCategoryIds: ReadonlySet<string>): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PARTNER_ONLY_CATEGORY_METADATA_KEY, PARTNER_ONLY_PRODUCT_TAG } from "@hartl-services/medusa-contracts/paths/affiliate";
|
|
2
|
+
import { collectCategoryIds } from "./category-filter.js";
|
|
3
|
+
function isFlagged(category) {
|
|
4
|
+
return category.metadata?.[PARTNER_ONLY_CATEGORY_METADATA_KEY] === true;
|
|
5
|
+
}
|
|
6
|
+
/** Ids of every flagged category and all its descendants. */
|
|
7
|
+
export function collectPartnerOnlyCategoryIds(trees) {
|
|
8
|
+
const ids = new Set();
|
|
9
|
+
const visit = (node) => {
|
|
10
|
+
if (isFlagged(node)) {
|
|
11
|
+
collectCategoryIds(node).forEach((id) => ids.add(id));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
node.category_children?.forEach(visit);
|
|
15
|
+
};
|
|
16
|
+
trees.forEach(visit);
|
|
17
|
+
return ids;
|
|
18
|
+
}
|
|
19
|
+
/** The trees without flagged categories (and their subcategories) - what a non-partner sees. */
|
|
20
|
+
export function withoutPartnerOnlyCategories(trees) {
|
|
21
|
+
return trees.filter((node) => !isFlagged(node)).map((node) => (node.category_children ? { ...node, category_children: withoutPartnerOnlyCategories(node.category_children) } : node));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A product only active partners may see and order: tagged
|
|
25
|
+
* `PARTNER_ONLY_PRODUCT_TAG` or in a partner-only category (see
|
|
26
|
+
* `collectPartnerOnlyCategoryIds`). The backend enforces the same rule when
|
|
27
|
+
* the cart is completed.
|
|
28
|
+
*/
|
|
29
|
+
export function isPartnerOnlyProduct(product, partnerOnlyCategoryIds) {
|
|
30
|
+
return (product.tags ?? []).some((tag) => tag.value === PARTNER_ONLY_PRODUCT_TAG) || (product.categories ?? []).some((category) => partnerOnlyCategoryIds.has(category.id));
|
|
31
|
+
}
|
|
@@ -10,6 +10,94 @@ export declare function createCustomerRegistrationSchema(t: Translate, policy: C
|
|
|
10
10
|
email: z.ZodString;
|
|
11
11
|
password: z.ZodString;
|
|
12
12
|
}, z.core.$strip>;
|
|
13
|
+
/** The address a registration may carry (stored as the customer's default address). */
|
|
14
|
+
export declare const registrationAddressSchema: z.ZodObject<{
|
|
15
|
+
address_1: z.ZodString;
|
|
16
|
+
city: z.ZodString;
|
|
17
|
+
company: z.ZodOptional<z.ZodString>;
|
|
18
|
+
country_code: z.ZodString;
|
|
19
|
+
postal_code: z.ZodString;
|
|
20
|
+
vat_number: z.ZodOptional<z.ZodString>;
|
|
21
|
+
}, z.core.$strip>;
|
|
22
|
+
/** What the server takes for a registration with profile (and optional address). */
|
|
23
|
+
export declare const customerRegistrationProfileSchema: z.ZodObject<{
|
|
24
|
+
email: z.ZodString;
|
|
25
|
+
password: z.ZodString;
|
|
26
|
+
address: z.ZodOptional<z.ZodObject<{
|
|
27
|
+
address_1: z.ZodString;
|
|
28
|
+
city: z.ZodString;
|
|
29
|
+
company: z.ZodOptional<z.ZodString>;
|
|
30
|
+
country_code: z.ZodString;
|
|
31
|
+
postal_code: z.ZodString;
|
|
32
|
+
vat_number: z.ZodOptional<z.ZodString>;
|
|
33
|
+
}, z.core.$strip>>;
|
|
34
|
+
first_name: z.ZodString;
|
|
35
|
+
last_name: z.ZodString;
|
|
36
|
+
phone: z.ZodString;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
/**
|
|
39
|
+
* A partner (e.g. therapist) registration: the profile with a complete
|
|
40
|
+
* company address - with a UID, or with the confirmation that the company has
|
|
41
|
+
* none - from which the partner application is submitted.
|
|
42
|
+
*/
|
|
43
|
+
export declare const partnerRegistrationProfileSchema: z.ZodObject<{
|
|
44
|
+
email: z.ZodString;
|
|
45
|
+
password: z.ZodString;
|
|
46
|
+
first_name: z.ZodString;
|
|
47
|
+
last_name: z.ZodString;
|
|
48
|
+
phone: z.ZodString;
|
|
49
|
+
address: z.ZodObject<{
|
|
50
|
+
address_1: z.ZodString;
|
|
51
|
+
city: z.ZodString;
|
|
52
|
+
country_code: z.ZodString;
|
|
53
|
+
postal_code: z.ZodString;
|
|
54
|
+
vat_number: z.ZodOptional<z.ZodString>;
|
|
55
|
+
company: z.ZodString;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
confirmed_without_vat_number: z.ZodBoolean;
|
|
58
|
+
}, z.core.$strip>;
|
|
59
|
+
/**
|
|
60
|
+
* The registration form with profile, address, a repeated password and the
|
|
61
|
+
* accepted terms. Address fields are all-or-nothing: none or a complete
|
|
62
|
+
* address.
|
|
63
|
+
*/
|
|
64
|
+
export declare function createCustomerRegistrationFormSchema(t: Translate, policy: CustomerPasswordPolicy | null): z.ZodObject<{
|
|
65
|
+
accept_terms: z.ZodBoolean;
|
|
66
|
+
address_1: z.ZodString;
|
|
67
|
+
city: z.ZodString;
|
|
68
|
+
confirmPassword: z.ZodString;
|
|
69
|
+
country_code: z.ZodString;
|
|
70
|
+
email: z.ZodString;
|
|
71
|
+
first_name: z.ZodString;
|
|
72
|
+
last_name: z.ZodString;
|
|
73
|
+
password: z.ZodString;
|
|
74
|
+
phone: z.ZodString;
|
|
75
|
+
postal_code: z.ZodString;
|
|
76
|
+
}, z.core.$strip>;
|
|
77
|
+
/**
|
|
78
|
+
* The partner registration form: profile and a required company address,
|
|
79
|
+
* an optional UID, and without UID the confirmation that the company has none.
|
|
80
|
+
*/
|
|
81
|
+
export declare function createPartnerRegistrationFormSchema(t: Translate, policy: CustomerPasswordPolicy | null): z.ZodObject<{
|
|
82
|
+
address_1: z.ZodString;
|
|
83
|
+
city: z.ZodString;
|
|
84
|
+
company: z.ZodString;
|
|
85
|
+
confirmed_without_vat_number: z.ZodBoolean;
|
|
86
|
+
country_code: z.ZodString;
|
|
87
|
+
postal_code: z.ZodString;
|
|
88
|
+
vat_number: z.ZodString;
|
|
89
|
+
accept_terms: z.ZodBoolean;
|
|
90
|
+
confirmPassword: z.ZodString;
|
|
91
|
+
email: z.ZodString;
|
|
92
|
+
first_name: z.ZodString;
|
|
93
|
+
last_name: z.ZodString;
|
|
94
|
+
password: z.ZodString;
|
|
95
|
+
phone: z.ZodString;
|
|
96
|
+
}, z.core.$strip>;
|
|
97
|
+
/** The partner registration form's values as the server takes them. */
|
|
98
|
+
export declare function toPartnerRegistrationProfile(value: PartnerRegistrationFormValues): PartnerRegistrationProfileValues;
|
|
99
|
+
/** The registration form's values as the server takes them. */
|
|
100
|
+
export declare function toRegistrationProfile(value: CustomerRegistrationFormValues): CustomerRegistrationProfileValues;
|
|
13
101
|
export declare function createPasswordResetSchema(t: Translate, policy: CustomerPasswordPolicy | null): z.ZodObject<{
|
|
14
102
|
confirmPassword: z.ZodString;
|
|
15
103
|
password: z.ZodString;
|
|
@@ -85,31 +173,35 @@ export declare const createCheckoutAddressSchema: (t: Translate, merchantCountry
|
|
|
85
173
|
has_different_billing_address: z.ZodBoolean;
|
|
86
174
|
shipping_address: z.ZodObject<{
|
|
87
175
|
address_1: z.ZodString;
|
|
88
|
-
address_2: z.ZodString;
|
|
89
176
|
city: z.ZodString;
|
|
90
177
|
company: z.ZodString;
|
|
91
178
|
country_code: z.ZodString;
|
|
179
|
+
postal_code: z.ZodString;
|
|
92
180
|
first_name: z.ZodString;
|
|
93
181
|
last_name: z.ZodString;
|
|
94
182
|
phone: z.ZodString;
|
|
95
|
-
|
|
183
|
+
address_2: z.ZodString;
|
|
96
184
|
province: z.ZodString;
|
|
97
185
|
}, z.core.$strip>;
|
|
98
186
|
billing_address: z.ZodObject<{
|
|
99
187
|
address_1: z.ZodString;
|
|
100
|
-
address_2: z.ZodString;
|
|
101
188
|
city: z.ZodString;
|
|
102
189
|
company: z.ZodString;
|
|
103
190
|
country_code: z.ZodString;
|
|
191
|
+
postal_code: z.ZodString;
|
|
104
192
|
first_name: z.ZodString;
|
|
105
193
|
last_name: z.ZodString;
|
|
106
194
|
phone: z.ZodString;
|
|
107
|
-
|
|
195
|
+
address_2: z.ZodString;
|
|
108
196
|
province: z.ZodString;
|
|
109
197
|
}, z.core.$strip>;
|
|
110
198
|
vat_number: z.ZodString;
|
|
111
199
|
}, z.core.$strip>;
|
|
112
200
|
export type CustomerAuthValues = z.infer<typeof customerAuthSchema>;
|
|
201
|
+
export type CustomerRegistrationProfileValues = z.infer<typeof customerRegistrationProfileSchema>;
|
|
202
|
+
export type CustomerRegistrationFormValues = z.infer<ReturnType<typeof createCustomerRegistrationFormSchema>>;
|
|
203
|
+
export type PartnerRegistrationProfileValues = z.infer<typeof partnerRegistrationProfileSchema>;
|
|
204
|
+
export type PartnerRegistrationFormValues = z.infer<ReturnType<typeof createPartnerRegistrationFormSchema>>;
|
|
113
205
|
export type LoginCodeRequestValues = z.infer<ReturnType<typeof createLoginCodeRequestSchema>>;
|
|
114
206
|
export type PasswordResetValues = z.infer<ReturnType<typeof createPasswordResetSchema>>;
|
|
115
207
|
export type VerificationResendValues = z.infer<ReturnType<typeof createVerificationResendSchema>>;
|
package/dist/core/form-values.js
CHANGED
|
@@ -27,6 +27,137 @@ export function createCustomerRegistrationSchema(t, policy) {
|
|
|
27
27
|
password: createNewPasswordSchema(t, policy),
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
|
+
/** The address a registration may carry (stored as the customer's default address). */
|
|
31
|
+
export const registrationAddressSchema = z.object({
|
|
32
|
+
address_1: z.string().trim().min(1),
|
|
33
|
+
city: z.string().trim().min(1),
|
|
34
|
+
company: z.string().trim().optional(),
|
|
35
|
+
country_code: z.string().trim().min(1),
|
|
36
|
+
postal_code: z.string().trim().min(1),
|
|
37
|
+
/** Stored in the address metadata, where the UID check reads it. */
|
|
38
|
+
vat_number: z.string().trim().optional(),
|
|
39
|
+
});
|
|
40
|
+
/** What the server takes for a registration with profile (and optional address). */
|
|
41
|
+
export const customerRegistrationProfileSchema = customerAuthSchema.extend({
|
|
42
|
+
address: registrationAddressSchema.optional(),
|
|
43
|
+
first_name: z.string().trim().min(1),
|
|
44
|
+
last_name: z.string().trim().min(1),
|
|
45
|
+
phone: z.string().trim(),
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* A partner (e.g. therapist) registration: the profile with a complete
|
|
49
|
+
* company address - with a UID, or with the confirmation that the company has
|
|
50
|
+
* none - from which the partner application is submitted.
|
|
51
|
+
*/
|
|
52
|
+
export const partnerRegistrationProfileSchema = customerRegistrationProfileSchema
|
|
53
|
+
.extend({
|
|
54
|
+
address: registrationAddressSchema.extend({ company: z.string().trim().min(1) }),
|
|
55
|
+
confirmed_without_vat_number: z.boolean(),
|
|
56
|
+
})
|
|
57
|
+
.refine((value) => Boolean(value.address.vat_number) || value.confirmed_without_vat_number, { path: ["confirmed_without_vat_number"] });
|
|
58
|
+
function registrationFormFields(t, policy) {
|
|
59
|
+
return {
|
|
60
|
+
accept_terms: z.boolean().refine((accepted) => accepted, t("registration.terms_required")),
|
|
61
|
+
address_1: z.string().trim(),
|
|
62
|
+
city: z.string().trim(),
|
|
63
|
+
confirmPassword: z.string(),
|
|
64
|
+
country_code: z.string().trim(),
|
|
65
|
+
email: z.string().trim().min(1, t("form.email_required")).email(t("form.email_invalid")),
|
|
66
|
+
first_name: z.string().trim().min(1, t("address.first_name_required")),
|
|
67
|
+
last_name: z.string().trim().min(1, t("address.last_name_required")),
|
|
68
|
+
password: createNewPasswordSchema(t, policy),
|
|
69
|
+
phone: z.string().trim(),
|
|
70
|
+
postal_code: z.string().trim(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The registration form with profile, address, a repeated password and the
|
|
75
|
+
* accepted terms. Address fields are all-or-nothing: none or a complete
|
|
76
|
+
* address.
|
|
77
|
+
*/
|
|
78
|
+
export function createCustomerRegistrationFormSchema(t, policy) {
|
|
79
|
+
return z
|
|
80
|
+
.object(registrationFormFields(t, policy))
|
|
81
|
+
.superRefine((value, context) => {
|
|
82
|
+
if (value.password !== value.confirmPassword) {
|
|
83
|
+
context.addIssue({ code: "custom", message: t("form.passwords_mismatch"), path: ["confirmPassword"] });
|
|
84
|
+
}
|
|
85
|
+
const addressFields = [
|
|
86
|
+
["address_1", "address.address_1_required"],
|
|
87
|
+
["postal_code", "address.postal_code_required"],
|
|
88
|
+
["city", "address.city_required"],
|
|
89
|
+
["country_code", "address.country_required"],
|
|
90
|
+
];
|
|
91
|
+
if (addressFields.some(([field]) => value[field])) {
|
|
92
|
+
for (const [field, code] of addressFields) {
|
|
93
|
+
if (!value[field]) {
|
|
94
|
+
context.addIssue({ code: "custom", message: t(code), path: [field] });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The partner registration form: profile and a required company address,
|
|
102
|
+
* an optional UID, and without UID the confirmation that the company has none.
|
|
103
|
+
*/
|
|
104
|
+
export function createPartnerRegistrationFormSchema(t, policy) {
|
|
105
|
+
return z
|
|
106
|
+
.object({
|
|
107
|
+
...registrationFormFields(t, policy),
|
|
108
|
+
address_1: z.string().trim().min(1, t("address.address_1_required")),
|
|
109
|
+
city: z.string().trim().min(1, t("address.city_required")),
|
|
110
|
+
company: z.string().trim().min(1, t("company_address.company_required")),
|
|
111
|
+
confirmed_without_vat_number: z.boolean(),
|
|
112
|
+
country_code: z.string().trim().min(1, t("address.country_required")),
|
|
113
|
+
postal_code: z.string().trim().min(1, t("address.postal_code_required")),
|
|
114
|
+
vat_number: z.string().refine((value) => {
|
|
115
|
+
const vatNumber = value.replace(/\s+/g, "");
|
|
116
|
+
return !vatNumber || (vatNumber.length >= 4 && vatNumber.length <= 32);
|
|
117
|
+
}, t("company_address.vat_number_invalid")),
|
|
118
|
+
})
|
|
119
|
+
.superRefine((value, context) => {
|
|
120
|
+
if (value.password !== value.confirmPassword) {
|
|
121
|
+
context.addIssue({ code: "custom", message: t("form.passwords_mismatch"), path: ["confirmPassword"] });
|
|
122
|
+
}
|
|
123
|
+
if (!value.vat_number.trim() && !value.confirmed_without_vat_number) {
|
|
124
|
+
context.addIssue({ code: "custom", message: t("affiliate_application.confirm_without_vat_number"), path: ["confirmed_without_vat_number"] });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/** The partner registration form's values as the server takes them. */
|
|
129
|
+
export function toPartnerRegistrationProfile(value) {
|
|
130
|
+
const vatNumber = value.vat_number.replace(/\s+/g, "").toUpperCase();
|
|
131
|
+
return {
|
|
132
|
+
address: {
|
|
133
|
+
address_1: value.address_1.trim(),
|
|
134
|
+
city: value.city.trim(),
|
|
135
|
+
company: value.company.trim(),
|
|
136
|
+
country_code: value.country_code.trim().toLowerCase(),
|
|
137
|
+
postal_code: value.postal_code.trim(),
|
|
138
|
+
...(vatNumber ? { vat_number: vatNumber } : {}),
|
|
139
|
+
},
|
|
140
|
+
// Only meaningful without a UID; the backend ignores it otherwise.
|
|
141
|
+
confirmed_without_vat_number: !vatNumber && value.confirmed_without_vat_number,
|
|
142
|
+
email: value.email.trim(),
|
|
143
|
+
first_name: value.first_name.trim(),
|
|
144
|
+
last_name: value.last_name.trim(),
|
|
145
|
+
password: value.password,
|
|
146
|
+
phone: value.phone.trim(),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** The registration form's values as the server takes them. */
|
|
150
|
+
export function toRegistrationProfile(value) {
|
|
151
|
+
const hasAddress = Boolean(value.address_1 && value.postal_code && value.city && value.country_code);
|
|
152
|
+
return {
|
|
153
|
+
email: value.email.trim(),
|
|
154
|
+
first_name: value.first_name.trim(),
|
|
155
|
+
last_name: value.last_name.trim(),
|
|
156
|
+
password: value.password,
|
|
157
|
+
phone: value.phone.trim(),
|
|
158
|
+
...(hasAddress ? { address: { address_1: value.address_1.trim(), city: value.city.trim(), country_code: value.country_code.trim().toLowerCase(), postal_code: value.postal_code.trim() } } : {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
30
161
|
export function createPasswordResetSchema(t, policy) {
|
|
31
162
|
return z
|
|
32
163
|
.object({
|
package/dist/core/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export * from "./cart/cart-tax-breakdown.js";
|
|
|
17
17
|
export * from "./cart/coupon-promotions.js";
|
|
18
18
|
export * from "./cart/payment-session.js";
|
|
19
19
|
export * from "./cart/refunded-cart.js";
|
|
20
|
+
export * from "./catalog/category-filter.js";
|
|
21
|
+
export * from "./catalog/partner-only.js";
|
|
20
22
|
export * from "./catalog/inventory.js";
|
|
21
23
|
export * from "./catalog/medusa-product-image.js";
|
|
22
24
|
export * from "./catalog/product-price.js";
|
package/dist/core/index.js
CHANGED
|
@@ -18,6 +18,8 @@ export * from "./cart/cart-tax-breakdown.js";
|
|
|
18
18
|
export * from "./cart/coupon-promotions.js";
|
|
19
19
|
export * from "./cart/payment-session.js";
|
|
20
20
|
export * from "./cart/refunded-cart.js";
|
|
21
|
+
export * from "./catalog/category-filter.js";
|
|
22
|
+
export * from "./catalog/partner-only.js";
|
|
21
23
|
export * from "./catalog/inventory.js";
|
|
22
24
|
export * from "./catalog/medusa-product-image.js";
|
|
23
25
|
export * from "./catalog/product-price.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Where an error happened; the shop's error tracker files it under this area. */
|
|
2
|
-
export type StorefrontErrorArea = "checkout" | "customer-auth" | "plugin-features" | "tax-compliance";
|
|
2
|
+
export type StorefrontErrorArea = "affiliate" | "checkout" | "customer-auth" | "plugin-features" | "tax-compliance";
|
|
3
3
|
export interface StorefrontErrorReport {
|
|
4
4
|
area: StorefrontErrorArea;
|
|
5
5
|
/** Extra data for tracing (cart id, status, ...); never addresses or payment details. */
|
package/dist/core/texts-de.d.ts
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
export declare const textsDe: {
|
|
7
7
|
readonly "form.password_required": "Bitte gib ein Passwort ein.";
|
|
8
8
|
readonly "form.passwords_mismatch": "Die Passwörter stimmen nicht überein.";
|
|
9
|
+
readonly "registration.terms_required": "Bitte akzeptiere die Nutzungsbedingungen und Datenschutzrichtlinien.";
|
|
10
|
+
readonly "registration.address_not_saved": "Dein Konto wurde angelegt, die Adresse konnte aber nicht gespeichert werden. Du kannst sie in deinem Konto nachtragen.";
|
|
11
|
+
readonly "partner_registration.unavailable": "Die Registrierung als Partner ist derzeit nicht möglich.";
|
|
12
|
+
readonly "partner_registration.application_not_submitted": "Dein Konto wurde angelegt, die Bewerbung konnte aber nicht übermittelt werden. Bitte melde dich bei uns.";
|
|
9
13
|
readonly "form.email_required": "Bitte gib deine E-Mail-Adresse ein.";
|
|
10
14
|
readonly "form.email_invalid": "Bitte gib eine gültige E-Mail-Adresse ein.";
|
|
11
15
|
readonly "form.login_code_invalid": "Bitte gib den {length}-stelligen Code ein.";
|
package/dist/core/texts-de.js
CHANGED
|
@@ -8,6 +8,10 @@ export const textsDe = {
|
|
|
8
8
|
// Forms
|
|
9
9
|
"form.password_required": "Bitte gib ein Passwort ein.",
|
|
10
10
|
"form.passwords_mismatch": "Die Passwörter stimmen nicht überein.",
|
|
11
|
+
"registration.terms_required": "Bitte akzeptiere die Nutzungsbedingungen und Datenschutzrichtlinien.",
|
|
12
|
+
"registration.address_not_saved": "Dein Konto wurde angelegt, die Adresse konnte aber nicht gespeichert werden. Du kannst sie in deinem Konto nachtragen.",
|
|
13
|
+
"partner_registration.unavailable": "Die Registrierung als Partner ist derzeit nicht möglich.",
|
|
14
|
+
"partner_registration.application_not_submitted": "Dein Konto wurde angelegt, die Bewerbung konnte aber nicht übermittelt werden. Bitte melde dich bei uns.",
|
|
11
15
|
"form.email_required": "Bitte gib deine E-Mail-Adresse ein.",
|
|
12
16
|
"form.email_invalid": "Bitte gib eine gültige E-Mail-Adresse ein.",
|
|
13
17
|
"form.login_code_invalid": "Bitte gib den {length}-stelligen Code ein.",
|
|
@@ -76,6 +76,16 @@ export declare function useAccountOrders(): {
|
|
|
76
76
|
isPending: boolean;
|
|
77
77
|
orders: HttpTypes.StoreOrder[];
|
|
78
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* One order of the account with its lines and totals, loaded when it is
|
|
81
|
+
* opened (the list carries only the total). Needs the shop's optional
|
|
82
|
+
* `getOrder` action; `null` loads nothing.
|
|
83
|
+
*/
|
|
84
|
+
export declare function useAccountOrderDetail(orderId: string | null): {
|
|
85
|
+
isError: boolean;
|
|
86
|
+
isPending: boolean;
|
|
87
|
+
order: HttpTypes.StoreOrder | null;
|
|
88
|
+
};
|
|
79
89
|
/** The account's address book page: add, edit (with the UID check), delete and default addresses. */
|
|
80
90
|
export declare function useAccountAddresses(): {
|
|
81
91
|
addressBook: {
|
|
@@ -145,6 +145,16 @@ export function useAccountOrders() {
|
|
|
145
145
|
orders: ordersResponse?.orders ?? [],
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* One order of the account with its lines and totals, loaded when it is
|
|
150
|
+
* opened (the list carries only the total). Needs the shop's optional
|
|
151
|
+
* `getOrder` action; `null` loads nothing.
|
|
152
|
+
*/
|
|
153
|
+
export function useAccountOrderDetail(orderId) {
|
|
154
|
+
const { queries } = useStorefront();
|
|
155
|
+
const { data: order, isError, isFetching } = useQuery({ ...queries.orderDetail(orderId ?? ""), enabled: Boolean(orderId) && queries.orderDetail(orderId ?? "").enabled !== false });
|
|
156
|
+
return { isError, isPending: Boolean(orderId) && isFetching && !order, order: order ?? null };
|
|
157
|
+
}
|
|
148
158
|
/** The account's address book page: add, edit (with the UID check), delete and default addresses. */
|
|
149
159
|
export function useAccountAddresses() {
|
|
150
160
|
const { actions, t, unwrapMedusaAction } = useStorefront();
|
package/dist/react/use-auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StorefrontBrowser } from "../browser/index.js";
|
|
2
|
-
import type { CustomerAuthValues, PasswordResetValues, VerificationResendValues } from "../core/form-values.js";
|
|
2
|
+
import type { CustomerAuthValues, CustomerRegistrationProfileValues, PartnerRegistrationProfileValues, PasswordResetValues, VerificationResendValues } from "../core/form-values.js";
|
|
3
3
|
/**
|
|
4
4
|
* Start of the backend's 400 for a registration with an address that already
|
|
5
5
|
* has an account; the form then links to the password reset.
|
|
@@ -48,6 +48,21 @@ export declare function usePasswordAuth({ getValues, onSuccess }: PasswordAuthOp
|
|
|
48
48
|
cancelVerification: () => void;
|
|
49
49
|
login: (value: CustomerAuthValues) => Promise<boolean>;
|
|
50
50
|
register: (value: CustomerAuthValues) => Promise<boolean>;
|
|
51
|
+
/** The registration did not store its address; the account exists (add it in the account). */
|
|
52
|
+
isAddressNotSaved: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Registration with name, phone and address; needs the shop's optional
|
|
55
|
+
* `registerCustomerWithProfile` action. True once signed in.
|
|
56
|
+
*/
|
|
57
|
+
registerWithProfile(value: CustomerRegistrationProfileValues): Promise<boolean>;
|
|
58
|
+
/** The partner registration did not submit its application; the account exists. */
|
|
59
|
+
isApplicationNotSubmitted: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Partner registration with company address; the application follows
|
|
62
|
+
* (after an e-mail confirmation: at the first login). Needs the shop's
|
|
63
|
+
* optional `registerPartnerWithProfile` action. True once signed in.
|
|
64
|
+
*/
|
|
65
|
+
registerPartner(value: PartnerRegistrationProfileValues): Promise<boolean>;
|
|
51
66
|
/** "I confirmed it": signs in again with the kept form values, also after a registration (the account exists by now). */
|
|
52
67
|
continueAfterVerification(): Promise<void>;
|
|
53
68
|
};
|