@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.
Files changed (44) hide show
  1. package/README.md +24 -0
  2. package/dist/browser/actions.d.ts +6 -0
  3. package/dist/browser/cart-mutations.d.ts +2 -1
  4. package/dist/browser/cart-mutations.js +2 -2
  5. package/dist/browser/index.d.ts +5 -1
  6. package/dist/browser/query-keys.d.ts +1 -0
  7. package/dist/browser/query-keys.js +2 -0
  8. package/dist/browser/query-options.d.ts +4 -0
  9. package/dist/browser/query-options.js +6 -0
  10. package/dist/core/catalog/category-filter.d.ts +16 -0
  11. package/dist/core/catalog/category-filter.js +33 -0
  12. package/dist/core/catalog/partner-only.d.ts +18 -0
  13. package/dist/core/catalog/partner-only.js +31 -0
  14. package/dist/core/form-values.d.ts +96 -4
  15. package/dist/core/form-values.js +131 -0
  16. package/dist/core/index.d.ts +2 -0
  17. package/dist/core/index.js +2 -0
  18. package/dist/core/report-error.d.ts +1 -1
  19. package/dist/core/texts-de.d.ts +4 -0
  20. package/dist/core/texts-de.js +4 -0
  21. package/dist/react/use-account.d.ts +10 -0
  22. package/dist/react/use-account.js +10 -0
  23. package/dist/react/use-auth.d.ts +16 -1
  24. package/dist/react/use-auth.js +67 -0
  25. package/dist/react/use-cart.d.ts +26 -1
  26. package/dist/react/use-cart.js +50 -3
  27. package/dist/react/use-checkout-address-form.d.ts +12 -12
  28. package/dist/react/use-checkout.d.ts +8 -8
  29. package/dist/registry/account.json +2 -2
  30. package/dist/registry/affiliate.json +2 -2
  31. package/dist/registry/cart.json +1 -1
  32. package/dist/registry/checkout.json +1 -1
  33. package/dist/registry/form.json +1 -1
  34. package/dist/registry/registry.json +9 -9
  35. package/dist/registry/setup.json +3 -3
  36. package/dist/server/affiliate.d.ts +4 -1
  37. package/dist/server/affiliate.js +29 -0
  38. package/dist/server/catalog.d.ts +17 -3
  39. package/dist/server/catalog.js +54 -6
  40. package/dist/server/customer.d.ts +39 -2
  41. package/dist/server/customer.js +174 -4
  42. package/dist/server/index.d.ts +25 -1
  43. package/dist/server/index.js +4 -3
  44. package/package.json +2 -2
@@ -1,7 +1,14 @@
1
+ import { cache } from "react";
2
+ import { collectPartnerOnlyCategoryIds, isPartnerOnlyProduct, withoutPartnerOnlyCategories } from "../core/catalog/partner-only.js";
1
3
  import { CATEGORY_FIELDS, COLLECTION_FIELDS, PRODUCT_FIELDS } from "../core/fields.js";
2
4
  const catalogFetchLimit = 100;
3
- /** Medusa's half of a catalog: products priced for a country, categories and collections. */
4
- export function createCatalog(context, region) {
5
+ /**
6
+ * Medusa's half of a catalog: products priced for a country, categories and
7
+ * collections. Partner-only products and categories (see
8
+ * `isPartnerOnlyProduct`) are left out unless `isActivePartner` says the
9
+ * visitor is an active partner; the backend refuses them at checkout anyway.
10
+ */
11
+ export function createCatalog(context, region, isActivePartner = async () => false) {
5
12
  async function getPricingContext(countryCode) {
6
13
  const countryRegion = await region.getRegionForCountry(countryCode);
7
14
  if (!countryRegion) {
@@ -13,8 +20,31 @@ export function createCatalog(context, region) {
13
20
  region_id: countryRegion.id,
14
21
  };
15
22
  }
23
+ // Every top-level category with all descendants, once per request.
24
+ const loadCategoryTrees = cache(async () => {
25
+ const sdk = context.createMedusaClient();
26
+ const { product_categories: productCategories } = await sdk.store.category.list({
27
+ fields: "id,name,handle,description,metadata,rank,*category_children",
28
+ include_descendants_tree: true,
29
+ limit: catalogFetchLimit,
30
+ parent_category_id: "null",
31
+ });
32
+ return productCategories;
33
+ });
34
+ /**
35
+ * Leaves out partner-only products for everyone but an active partner. The
36
+ * partner check (a backend request) only runs when a partner-only product
37
+ * is among them, so a shop without any pays nothing for it; the category
38
+ * trees are only read when a product has categories.
39
+ */
40
+ async function visibleProducts(products) {
41
+ const hasCategories = products.some((product) => (product.categories?.length ?? 0) > 0);
42
+ const partnerOnlyCategoryIds = hasCategories ? collectPartnerOnlyCategoryIds(await loadCategoryTrees()) : new Set();
43
+ const visible = products.filter((product) => !isPartnerOnlyProduct(product, partnerOnlyCategoryIds));
44
+ return visible.length === products.length || !(await isActivePartner()) ? visible : products;
45
+ }
16
46
  return {
17
- async listCommerceProducts({ categoryId, collectionId, countryCode, handles }) {
47
+ async listCommerceProducts({ categoryId, collectionId, countryCode, handles, q }) {
18
48
  const pricingContext = await getPricingContext(countryCode);
19
49
  if (!pricingContext) {
20
50
  return null;
@@ -25,10 +55,11 @@ export function createCatalog(context, region) {
25
55
  ...(categoryId ? { category_id: [categoryId] } : {}),
26
56
  ...(collectionId ? { collection_id: [collectionId] } : {}),
27
57
  ...(handles?.length ? { handle: handles } : {}),
58
+ ...(q?.trim() ? { q: q.trim() } : {}),
28
59
  fields: PRODUCT_FIELDS,
29
60
  limit: catalogFetchLimit,
30
61
  });
31
- return products;
62
+ return await visibleProducts(products);
32
63
  },
33
64
  async commerceProductByHandle({ countryCode, handle }) {
34
65
  const pricingContext = await getPricingContext(countryCode);
@@ -42,7 +73,20 @@ export function createCatalog(context, region) {
42
73
  handle,
43
74
  limit: 1,
44
75
  });
45
- return products[0] ?? null;
76
+ return (await visibleProducts(products))[0] ?? null;
77
+ },
78
+ /**
79
+ * The top-level categories with all descendants (e.g. "Anwendungsbereiche"
80
+ * and "Inhaltsstoffe" with their entries) - the filter trees of a product
81
+ * list; filter with `filterProductsByCategories` from /core. Partner-only
82
+ * categories only for an active partner.
83
+ */
84
+ async listCategoryTrees() {
85
+ const trees = await loadCategoryTrees();
86
+ if (collectPartnerOnlyCategoryIds(trees).size === 0) {
87
+ return trees;
88
+ }
89
+ return (await isActivePartner()) ? trees : withoutPartnerOnlyCategories(trees);
46
90
  },
47
91
  async categoryByPath(categoryPath) {
48
92
  const sdk = context.createMedusaClient();
@@ -51,7 +95,11 @@ export function createCatalog(context, region) {
51
95
  handle: categoryPath.join("/"),
52
96
  limit: 1,
53
97
  });
54
- return productCategories[0] ?? null;
98
+ const category = productCategories[0] ?? null;
99
+ if (!category || !collectPartnerOnlyCategoryIds(await loadCategoryTrees()).has(category.id)) {
100
+ return category;
101
+ }
102
+ return (await isActivePartner()) ? category : null;
55
103
  },
56
104
  async collectionByHandle(handle) {
57
105
  const sdk = context.createMedusaClient();
@@ -1,6 +1,7 @@
1
1
  import type { LoginWithCodeRequest, RequestLoginCodeRequest } from "@hartl-services/medusa-contracts/types/customer-auth-store";
2
- import type { CheckoutAddressValues, CustomerAddressValues, CustomerAuthValues, CustomerProfileValues, VerificationResendValues } from "../core/form-values.js";
2
+ import type { CheckoutAddressValues, CustomerAddressValues, CustomerAuthValues, CustomerProfileValues, CustomerRegistrationProfileValues, PartnerRegistrationProfileValues, VerificationResendValues } from "../core/form-values.js";
3
3
  import type { ServerContext } from "./context.js";
4
+ import type { ServerPluginFeatures } from "./plugin-features.js";
4
5
  import type { RunMedusaAction } from "./run-medusa-action.js";
5
6
  export interface UpdateCustomerAddressInput {
6
7
  addressId: string;
@@ -10,7 +11,7 @@ export interface UpdateCustomerAddressInput {
10
11
  metadata?: Record<string, unknown>;
11
12
  };
12
13
  }
13
- export declare function createCustomer(context: ServerContext, runMedusaAction: RunMedusaAction): {
14
+ export declare function createCustomer(context: ServerContext, runMedusaAction: RunMedusaAction, pluginFeatures: ServerPluginFeatures): {
14
15
  retrieveCustomer(): Promise<import("@medusajs/types").StoreCustomerResponse | null>;
15
16
  loginCustomer(data: CustomerAuthValues): Promise<import("../core/action-result.js").MedusaActionResult<{
16
17
  state: "success";
@@ -31,6 +32,42 @@ export declare function createCustomer(context: ServerContext, runMedusaAction:
31
32
  state: "success";
32
33
  sessionToken: string;
33
34
  }>>;
35
+ /**
36
+ * Registration with name, phone and an optional address (the default
37
+ * address). The account counts as registered even if only the address
38
+ * fails (`addressSaved: false`, to add it in the account later) - a retry
39
+ * would otherwise end in "e-mail exists". While the e-mail is unconfirmed
40
+ * the address waits in the customer's metadata until the first login.
41
+ */
42
+ registerCustomerWithProfile(data: CustomerRegistrationProfileValues): Promise<import("../core/action-result.js").MedusaActionResult<{
43
+ state: "verification_required";
44
+ addressId?: undefined;
45
+ addressSaved?: undefined;
46
+ sessionToken?: undefined;
47
+ } | {
48
+ addressSaved: boolean;
49
+ sessionToken: string;
50
+ state: "success";
51
+ }>>;
52
+ /**
53
+ * Partner (e.g. therapist) registration: the account with its company
54
+ * address as default address, then the partner application from that
55
+ * address. Like the plain registration it counts as registered even if a
56
+ * later step fails (`addressSaved` / `applicationSubmitted` false). While
57
+ * the e-mail is unconfirmed, address and application wait until the
58
+ * first login. Refused up front while the partner portal is off.
59
+ */
60
+ registerPartnerWithProfile(data: PartnerRegistrationProfileValues): Promise<import("../core/action-result.js").MedusaActionResult<{
61
+ state: "verification_required";
62
+ addressId?: undefined;
63
+ addressSaved?: undefined;
64
+ sessionToken?: undefined;
65
+ } | {
66
+ addressSaved: boolean;
67
+ applicationSubmitted: boolean;
68
+ sessionToken: string;
69
+ state: "success";
70
+ }>>;
34
71
  requestLoginCode(data: RequestLoginCodeRequest): Promise<import("../core/action-result.js").MedusaActionResult<void>>;
35
72
  loginWithCode(data: LoginWithCodeRequest): Promise<import("../core/action-result.js").MedusaActionResult<{
36
73
  state: "success";
@@ -1,12 +1,15 @@
1
1
  import { customerAuthStorePaths } from "@hartl-services/medusa-contracts/paths/customer-auth";
2
2
  import { EMAIL_OTP_AUTH_PROVIDER, LoginWithCodeSchema, RequestLoginCodeSchema, normalizeLoginEmail } from "@hartl-services/medusa-contracts/schemas/customer-auth";
3
3
  import { customerVerificationStorePaths } from "@hartl-services/medusa-contracts/paths/customer-verification";
4
+ import { affiliateStorePaths } from "@hartl-services/medusa-contracts/paths/affiliate";
5
+ import { TAX_COMPLIANCE_FEATURES, TAX_COMPLIANCE_PLUGIN_KEY, taxCompliancePaths } from "@hartl-services/medusa-contracts/paths/tax-compliance";
4
6
  import { vatNumberMetadataPatch } from "../core/addresses.js";
5
7
  import { completeCustomerSignIn as completeCustomerSignInWith, loginWithEmailpass } from "../core/auth/customer-sign-in.js";
6
8
  import { toCustomerAddressesFromCheckout } from "../core/checkout/checkout-address-draft.js";
7
9
  import { StorefrontError } from "../core/errors.js";
8
10
  import { CART_OWNER_FIELDS, CUSTOMER_FIELDS } from "../core/fields.js";
9
- import { createCheckoutAddressSchema, createVerificationResendSchema, customerAddressSchema, customerAuthSchema, customerProfileSchema } from "../core/form-values.js";
11
+ import { createCheckoutAddressSchema, createVerificationResendSchema, customerAddressSchema, customerAuthSchema, customerProfileSchema, customerRegistrationProfileSchema, partnerRegistrationProfileSchema, registrationAddressSchema } from "../core/form-values.js";
12
+ import { isPluginFeatureEnabled } from "../core/plugin-features.js";
10
13
  function cartCustomerHasAccount(cart) {
11
14
  const customer = Reflect.get(cart, "customer");
12
15
  return typeof customer === "object" && customer !== null && Reflect.get(customer, "has_account") === true;
@@ -14,10 +17,29 @@ function cartCustomerHasAccount(cart) {
14
17
  function isVerificationRequired(result) {
15
18
  return typeof result === "object" && result !== null && Reflect.get(result, "verification_required") === true && typeof Reflect.get(result, "token") === "string";
16
19
  }
20
+ /**
21
+ * Metadata key of the address a registration could not store yet: Medusa takes
22
+ * no address with the customer, and while the e-mail is unconfirmed there is
23
+ * no token to add one, so it waits here until the first login.
24
+ */
25
+ const PENDING_REGISTRATION_ADDRESS_KEY = "pending_registration_address";
26
+ /**
27
+ * Metadata key of a partner application that waits for its company address
28
+ * (the registration address): submitted right after the address is stored.
29
+ */
30
+ const PENDING_PARTNER_APPLICATION_KEY = "pending_partner_application";
31
+ function toPendingPartnerApplication(value) {
32
+ return typeof value === "object" && value !== null ? { confirmed_without_vat_number: Reflect.get(value, "confirmed_without_vat_number") === true } : null;
33
+ }
34
+ /** The registration address the customer create took last (Medusa answers with the whole customer). */
35
+ function findDefaultAddressId(customer) {
36
+ const addresses = customer.addresses ?? [];
37
+ return (addresses.find(({ is_default_billing }) => is_default_billing) ?? addresses[0])?.id ?? null;
38
+ }
17
39
  function getAuthenticationToken(result) {
18
40
  return typeof result === "string" ? result : null;
19
41
  }
20
- export function createCustomer(context, runMedusaAction) {
42
+ export function createCustomer(context, runMedusaAction, pluginFeatures) {
21
43
  const { createAuthenticatedMedusaClient, createMedusaClient, session } = context;
22
44
  async function requestCustomerVerification(email, token) {
23
45
  const sdk = createAuthenticatedMedusaClient(token);
@@ -71,12 +93,123 @@ export function createCustomer(context, runMedusaAction) {
71
93
  return undefined;
72
94
  }
73
95
  }
74
- function completeCustomerSignIn(token, email) {
75
- return completeCustomerSignInWith(token, email, {
96
+ /**
97
+ * Stores the registration address as the customer's default address.
98
+ * `saved` is true when stored (or nothing to store); never throws - a
99
+ * failing address must not turn a registration or login into a failure.
100
+ */
101
+ async function storeRegistrationAddress(token, profile, address) {
102
+ const parsed = registrationAddressSchema.safeParse(address);
103
+ if (!parsed.success) {
104
+ return { addressId: null, saved: true };
105
+ }
106
+ try {
107
+ const sdk = createAuthenticatedMedusaClient(token);
108
+ const { vat_number: vatNumber, ...addressValues } = parsed.data;
109
+ const { customer } = await sdk.store.customer.createAddress({
110
+ ...addressValues,
111
+ first_name: profile.first_name ?? undefined,
112
+ is_default_billing: true,
113
+ is_default_shipping: true,
114
+ last_name: profile.last_name ?? undefined,
115
+ phone: profile.phone || undefined,
116
+ ...(vatNumber ? { metadata: vatNumberMetadataPatch(vatNumber) } : {}),
117
+ }, { fields: "*addresses" });
118
+ await sdk.store.customer.update({ metadata: { [PENDING_REGISTRATION_ADDRESS_KEY]: null } });
119
+ return { addressId: findDefaultAddressId(customer), saved: true };
120
+ }
121
+ catch (error) {
122
+ context.reportError(error, { area: "customer-auth", context: { step: "registration_address" } });
123
+ return { addressId: null, saved: false };
124
+ }
125
+ }
126
+ /**
127
+ * Submits a registration's partner application with its company address:
128
+ * a UID is checked first (the application takes it only once checked; an
129
+ * unreachable check counts too), and the pending marker is cleared either
130
+ * way. True when submitted; never throws - the account stays registered.
131
+ */
132
+ async function submitPartnerApplication(token, addressId, hasVatNumber, application) {
133
+ const sdk = createAuthenticatedMedusaClient(token);
134
+ try {
135
+ if (hasVatNumber && isPluginFeatureEnabled(await pluginFeatures.loadPluginFeatures(), TAX_COMPLIANCE_PLUGIN_KEY, TAX_COMPLIANCE_FEATURES.vatValidation)) {
136
+ await sdk.client.fetch(taxCompliancePaths.store.customers.addressTaxIdentification(addressId), { method: "POST" });
137
+ }
138
+ await sdk.client.fetch(affiliateStorePaths.post.register, {
139
+ body: { confirmed_without_vat_number: application.confirmed_without_vat_number, customer_address_id: addressId },
140
+ method: "POST",
141
+ });
142
+ return true;
143
+ }
144
+ catch (error) {
145
+ context.reportError(error, { area: "affiliate", context: { step: "registration_application" } });
146
+ return false;
147
+ }
148
+ finally {
149
+ try {
150
+ await sdk.store.customer.update({ metadata: { [PENDING_PARTNER_APPLICATION_KEY]: null } });
151
+ }
152
+ catch (error) {
153
+ context.reportError(error, { area: "affiliate", context: { step: "registration_application_cleanup" } });
154
+ }
155
+ }
156
+ }
157
+ /**
158
+ * After a login: adds a registration address that waited for the e-mail
159
+ * confirmation, then the partner application that waited for it.
160
+ */
161
+ async function applyPendingRegistrationAddress(token) {
162
+ try {
163
+ const { customer } = await createAuthenticatedMedusaClient(token).store.customer.retrieve({ fields: "first_name,last_name,phone,metadata,*addresses" });
164
+ const pending = customer.metadata?.[PENDING_REGISTRATION_ADDRESS_KEY];
165
+ if (pending && (customer.addresses?.length ?? 0) === 0) {
166
+ const { addressId } = await storeRegistrationAddress(token, customer, pending);
167
+ const application = toPendingPartnerApplication(customer.metadata?.[PENDING_PARTNER_APPLICATION_KEY]);
168
+ if (addressId && application) {
169
+ await submitPartnerApplication(token, addressId, Boolean(registrationAddressSchema.safeParse(pending).data?.vat_number), application);
170
+ }
171
+ }
172
+ }
173
+ catch (error) {
174
+ context.reportError(error, { area: "customer-auth", context: { step: "pending_registration_address" } });
175
+ }
176
+ }
177
+ async function completeCustomerSignIn(token, email) {
178
+ const result = await completeCustomerSignInWith(token, email, {
76
179
  createAuthenticatedClient: createAuthenticatedMedusaClient,
77
180
  storeToken: session.setMedusaAuthToken,
78
181
  transferCart: transferActiveCartToCustomer,
79
182
  });
183
+ await applyPendingRegistrationAddress(result.sessionToken);
184
+ return result;
185
+ }
186
+ async function registerWithProfile(parsed, metadata = {}) {
187
+ const credentials = { email: parsed.email, password: parsed.password };
188
+ const registrationSdk = createMedusaClient();
189
+ const registrationToken = await registrationSdk.auth.register("customer", "emailpass", credentials);
190
+ const customerSdk = createAuthenticatedMedusaClient(registrationToken);
191
+ const pendingMetadata = { ...(parsed.address ? { [PENDING_REGISTRATION_ADDRESS_KEY]: parsed.address } : {}), ...metadata };
192
+ const { customer } = await customerSdk.store.customer.create({
193
+ email: parsed.email,
194
+ first_name: parsed.first_name,
195
+ last_name: parsed.last_name,
196
+ ...(parsed.phone ? { phone: parsed.phone } : {}),
197
+ ...(Object.keys(pendingMetadata).length > 0 ? { metadata: pendingMetadata } : {}),
198
+ });
199
+ const loginSdk = createMedusaClient();
200
+ const result = await loginSdk.auth.login("customer", "emailpass", credentials);
201
+ if (isVerificationRequired(result)) {
202
+ await requestCustomerVerification(parsed.email, result.token);
203
+ return { state: "verification_required" };
204
+ }
205
+ const token = getAuthenticationToken(result);
206
+ if (!token) {
207
+ throw new StorefrontError("auth.registration_unsupported_step");
208
+ }
209
+ await transferActiveCartToCustomer(createAuthenticatedMedusaClient(token), { id: customer.id, email: parsed.email });
210
+ await session.setMedusaAuthToken(token);
211
+ const { addressId, saved } = parsed.address ? await storeRegistrationAddress(token, parsed, parsed.address) : { addressId: null, saved: true };
212
+ return { addressId, addressSaved: saved, state: "success", sessionToken: token };
80
213
  }
81
214
  async function signInCustomer(data, { requestVerificationMail }) {
82
215
  const parsed = customerAuthSchema.parse(data);
@@ -144,6 +277,43 @@ export function createCustomer(context, runMedusaAction) {
144
277
  return { state: "success", sessionToken: token };
145
278
  }, { endSessionOnUnauthorized: false });
146
279
  },
280
+ /**
281
+ * Registration with name, phone and an optional address (the default
282
+ * address). The account counts as registered even if only the address
283
+ * fails (`addressSaved: false`, to add it in the account later) - a retry
284
+ * would otherwise end in "e-mail exists". While the e-mail is unconfirmed
285
+ * the address waits in the customer's metadata until the first login.
286
+ */
287
+ async registerCustomerWithProfile(data) {
288
+ const parsed = customerRegistrationProfileSchema.parse(data);
289
+ return runMedusaAction(async () => {
290
+ const result = await registerWithProfile(parsed);
291
+ return result.state === "success" ? { addressSaved: result.addressSaved, sessionToken: result.sessionToken, state: result.state } : result;
292
+ }, { endSessionOnUnauthorized: false });
293
+ },
294
+ /**
295
+ * Partner (e.g. therapist) registration: the account with its company
296
+ * address as default address, then the partner application from that
297
+ * address. Like the plain registration it counts as registered even if a
298
+ * later step fails (`addressSaved` / `applicationSubmitted` false). While
299
+ * the e-mail is unconfirmed, address and application wait until the
300
+ * first login. Refused up front while the partner portal is off.
301
+ */
302
+ async registerPartnerWithProfile(data) {
303
+ const parsed = partnerRegistrationProfileSchema.parse(data);
304
+ return runMedusaAction(async () => {
305
+ if (!isPluginFeatureEnabled(await pluginFeatures.loadPluginFeatures(), "affiliate", "partner_portal")) {
306
+ throw new StorefrontError("partner_registration.unavailable");
307
+ }
308
+ const application = { confirmed_without_vat_number: parsed.confirmed_without_vat_number };
309
+ const result = await registerWithProfile(parsed, { [PENDING_PARTNER_APPLICATION_KEY]: application });
310
+ if (result.state !== "success") {
311
+ return result;
312
+ }
313
+ const applicationSubmitted = result.addressId ? await submitPartnerApplication(result.sessionToken, result.addressId, Boolean(parsed.address.vat_number), application) : false;
314
+ return { addressSaved: result.addressSaved, applicationSubmitted, sessionToken: result.sessionToken, state: result.state };
315
+ }, { endSessionOnUnauthorized: false });
316
+ },
147
317
  // The backend answers 202 for every address (no account enumeration) and
148
318
  // sends at most one code per address and minute.
149
319
  async requestLoginCode(data) {
@@ -61,6 +61,27 @@ export declare function createStorefrontServer(config: StorefrontServerConfig):
61
61
  state: "success";
62
62
  sessionToken: string;
63
63
  }>>;
64
+ registerCustomerWithProfile(data: import("../core/form-values.js").CustomerRegistrationProfileValues): Promise<import("../core/action-result.js").MedusaActionResult<{
65
+ state: "verification_required";
66
+ addressId?: undefined;
67
+ addressSaved?: undefined;
68
+ sessionToken?: undefined;
69
+ } | {
70
+ addressSaved: boolean;
71
+ sessionToken: string;
72
+ state: "success";
73
+ }>>;
74
+ registerPartnerWithProfile(data: import("../core/form-values.js").PartnerRegistrationProfileValues): Promise<import("../core/action-result.js").MedusaActionResult<{
75
+ state: "verification_required";
76
+ addressId?: undefined;
77
+ addressSaved?: undefined;
78
+ sessionToken?: undefined;
79
+ } | {
80
+ addressSaved: boolean;
81
+ applicationSubmitted: boolean;
82
+ sessionToken: string;
83
+ state: "success";
84
+ }>>;
64
85
  requestLoginCode(data: import("@hartl-services/medusa-contracts/types/customer-auth-store").RequestLoginCodeRequest): Promise<import("../core/action-result.js").MedusaActionResult<void>>;
65
86
  loginWithCode(data: import("@hartl-services/medusa-contracts/types/customer-auth-store").LoginWithCodeRequest): Promise<import("../core/action-result.js").MedusaActionResult<{
66
87
  state: "success";
@@ -100,6 +121,8 @@ export declare function createStorefrontServer(config: StorefrontServerConfig):
100
121
  verifyCustomerAddressTaxIdentification(addressId: string): Promise<import("../core/action-result.js").MedusaActionResult<import("@hartl-services/medusa-contracts/types/tax-compliance-store").SubmitCustomerAddressTaxIdentificationResponse>>;
101
122
  };
102
123
  affiliate: {
124
+ loadPartnerPortalState: () => Promise<import("@hartl-services/medusa-contracts/types/affiliate-store").StoreAffiliatePortalContextResponse["portal_state"] | null>;
125
+ isActivePartner(): Promise<boolean>;
103
126
  submitAffiliateApplication(data: import("@hartl-services/medusa-contracts/types/affiliate-store").SubmitAffiliateApplicationRequest): Promise<import("../core/action-result.js").MedusaActionResult<{
104
127
  application: {
105
128
  id: string;
@@ -236,11 +259,12 @@ export declare function createStorefrontServer(config: StorefrontServerConfig):
236
259
  }): Promise<Response>;
237
260
  };
238
261
  catalog: {
239
- listCommerceProducts({ categoryId, collectionId, countryCode, handles }: import("./catalog.js").CommerceProductListInput): Promise<import("@medusajs/types").StoreProduct[] | null>;
262
+ listCommerceProducts({ categoryId, collectionId, countryCode, handles, q }: import("./catalog.js").CommerceProductListInput): Promise<import("@medusajs/types").StoreProduct[] | null>;
240
263
  commerceProductByHandle({ countryCode, handle }: {
241
264
  countryCode: string;
242
265
  handle: string;
243
266
  }): Promise<import("@medusajs/types").StoreProduct | null>;
267
+ listCategoryTrees(): Promise<import("@medusajs/types").StoreProductCategory[]>;
244
268
  categoryByPath(categoryPath: string[]): Promise<import("@medusajs/types").StoreProductCategory | null>;
245
269
  collectionByHandle(handle: string): Promise<import("@medusajs/types").StoreCollection | null>;
246
270
  };
@@ -26,6 +26,7 @@ export function createStorefrontServer(config) {
26
26
  const runMedusaAction = createRunMedusaAction(context);
27
27
  const pluginFeatures = createPluginFeatures(context);
28
28
  const region = createRegion(context);
29
+ const affiliate = createAffiliate(context, pluginFeatures, runMedusaAction);
29
30
  return {
30
31
  session: context.session,
31
32
  createMedusaClient: context.createMedusaClient,
@@ -34,12 +35,12 @@ export function createStorefrontServer(config) {
34
35
  pluginFeatures,
35
36
  region,
36
37
  cart: createCart(context, pluginFeatures),
37
- customer: createCustomer(context, runMedusaAction),
38
+ customer: createCustomer(context, runMedusaAction, pluginFeatures),
38
39
  checkout: createCheckout(context),
39
40
  taxCompliance: createTaxCompliance(context, pluginFeatures, runMedusaAction),
40
- affiliate: createAffiliate(context, pluginFeatures, runMedusaAction),
41
+ affiliate,
41
42
  orders: createOrders(context),
42
43
  documents: createDocuments(context),
43
- catalog: createCatalog(context, region),
44
+ catalog: createCatalog(context, region, affiliate.isActivePartner),
44
45
  };
45
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hartl-services/medusa-storefront",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Headless Medusa storefront logic, Next.js server/browser adapters and React hooks for shops built from the @hartl-services/medusa-* plugins.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -45,7 +45,7 @@
45
45
  }
46
46
  },
47
47
  "dependencies": {
48
- "@hartl-services/medusa-contracts": "^1.1.0",
48
+ "@hartl-services/medusa-contracts": "^1.2.0",
49
49
  "server-only": "^0.0.1"
50
50
  },
51
51
  "peerDependencies": {