@hartl-services/medusa-storefront 0.2.0 → 0.4.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 (36) hide show
  1. package/README.md +13 -0
  2. package/dist/browser/actions.d.ts +2 -0
  3. package/dist/browser/index.d.ts +32 -0
  4. package/dist/browser/query-options.d.ts +32 -0
  5. package/dist/core/affiliate/consignment-line-labels.d.ts +19 -0
  6. package/dist/core/affiliate/consignment-line-labels.js +18 -0
  7. package/dist/core/catalog/partner-only.d.ts +18 -0
  8. package/dist/core/catalog/partner-only.js +31 -0
  9. package/dist/core/form-values.d.ts +51 -2
  10. package/dist/core/form-values.js +73 -7
  11. package/dist/core/index.d.ts +2 -0
  12. package/dist/core/index.js +2 -0
  13. package/dist/core/report-error.d.ts +1 -1
  14. package/dist/core/texts-de.d.ts +2 -0
  15. package/dist/core/texts-de.js +2 -0
  16. package/dist/react/use-affiliate.d.ts +8 -0
  17. package/dist/react/use-auth.d.ts +9 -1
  18. package/dist/react/use-auth.js +35 -0
  19. package/dist/react/use-checkout-address-form.d.ts +6 -6
  20. package/dist/react/use-checkout.d.ts +4 -4
  21. package/dist/registry/account.json +2 -2
  22. package/dist/registry/affiliate.json +4 -4
  23. package/dist/registry/cart.json +1 -1
  24. package/dist/registry/checkout.json +1 -1
  25. package/dist/registry/form.json +1 -1
  26. package/dist/registry/registry.json +9 -9
  27. package/dist/registry/setup.json +3 -3
  28. package/dist/server/affiliate.d.ts +8 -1
  29. package/dist/server/affiliate.js +29 -0
  30. package/dist/server/catalog.d.ts +9 -3
  31. package/dist/server/catalog.js +46 -14
  32. package/dist/server/customer.d.ts +23 -2
  33. package/dist/server/customer.js +119 -35
  34. package/dist/server/index.d.ts +18 -0
  35. package/dist/server/index.js +4 -3
  36. package/package.json +2 -2
@@ -75,6 +75,8 @@ export function usePasswordAuth({ getValues, onSuccess }) {
75
75
  const [isContinuing, setIsContinuing] = useState(false);
76
76
  // A registration with profile succeeded but its address could not be stored.
77
77
  const [isAddressNotSaved, setIsAddressNotSaved] = useState(false);
78
+ // A partner registration succeeded but its application could not be submitted.
79
+ const [isApplicationNotSubmitted, setIsApplicationNotSubmitted] = useState(false);
78
80
  // The background check and the "continue" button must not sign in twice.
79
81
  const confirmationSignIn = useRef("idle");
80
82
  async function completeSignIn(sessionToken) {
@@ -162,6 +164,39 @@ export function usePasswordAuth({ getValues, onSuccess }) {
162
164
  return false;
163
165
  }
164
166
  },
167
+ /** The partner registration did not submit its application; the account exists. */
168
+ isApplicationNotSubmitted,
169
+ /**
170
+ * Partner registration with company address; the application follows
171
+ * (after an e-mail confirmation: at the first login). Needs the shop's
172
+ * optional `registerPartnerWithProfile` action. True once signed in.
173
+ */
174
+ async registerPartner(value) {
175
+ const action = actions.registerPartnerWithProfile;
176
+ setErrorMessage(null);
177
+ setIsAddressNotSaved(false);
178
+ setIsApplicationNotSubmitted(false);
179
+ if (!action) {
180
+ setErrorMessage(t("partner_registration.unavailable"));
181
+ return false;
182
+ }
183
+ try {
184
+ const result = unwrapMedusaAction(await action(value));
185
+ if (result.state === "verification_required") {
186
+ setVerification((current) => ({ email: value.email, stillUnverified: current !== null }));
187
+ return false;
188
+ }
189
+ setIsAddressNotSaved(!result.addressSaved);
190
+ setIsApplicationNotSubmitted(!result.applicationSubmitted);
191
+ await completeSignIn(result.sessionToken);
192
+ return true;
193
+ }
194
+ catch (error) {
195
+ setVerification(null);
196
+ setErrorMessage(t.message(getErrorMessage(error, t("auth.sign_in_failed"))));
197
+ return false;
198
+ }
199
+ },
165
200
  /** "I confirmed it": signs in again with the kept form values, also after a registration (the account exists by now). */
166
201
  async continueAfterVerification() {
167
202
  setIsContinuing(true);
@@ -35,25 +35,25 @@ export declare function useCheckoutAddressForm({ countryCode, countryOptions, in
35
35
  shipping_address: {
36
36
  address_1: string;
37
37
  city: string;
38
+ company: string;
38
39
  country_code: string;
39
40
  postal_code: string;
40
41
  first_name: string;
41
42
  last_name: string;
42
43
  phone: string;
43
44
  address_2: string;
44
- company: string;
45
45
  province: string;
46
46
  };
47
47
  billing_address: {
48
48
  address_1: string;
49
49
  city: string;
50
+ company: string;
50
51
  country_code: string;
51
52
  postal_code: string;
52
53
  first_name: string;
53
54
  last_name: string;
54
55
  phone: string;
55
56
  address_2: string;
56
- company: string;
57
57
  province: string;
58
58
  };
59
59
  vat_number: string;
@@ -69,25 +69,25 @@ export declare function useCheckoutAddressForm({ countryCode, countryOptions, in
69
69
  shipping_address: {
70
70
  address_1: string;
71
71
  city: string;
72
+ company: string;
72
73
  country_code: string;
73
74
  postal_code: string;
74
75
  first_name: string;
75
76
  last_name: string;
76
77
  phone: string;
77
78
  address_2: string;
78
- company: string;
79
79
  province: string;
80
80
  };
81
81
  billing_address: {
82
82
  address_1: string;
83
83
  city: string;
84
+ company: string;
84
85
  country_code: string;
85
86
  postal_code: string;
86
87
  first_name: string;
87
88
  last_name: string;
88
89
  phone: string;
89
90
  address_2: string;
90
- company: string;
91
91
  province: string;
92
92
  };
93
93
  vat_number: string;
@@ -105,25 +105,25 @@ export declare function useCheckoutAddressForm({ countryCode, countryOptions, in
105
105
  shipping_address: import("zod").ZodObject<{
106
106
  address_1: import("zod").ZodString;
107
107
  city: import("zod").ZodString;
108
+ company: import("zod").ZodString;
108
109
  country_code: import("zod").ZodString;
109
110
  postal_code: import("zod").ZodString;
110
111
  first_name: import("zod").ZodString;
111
112
  last_name: import("zod").ZodString;
112
113
  phone: import("zod").ZodString;
113
114
  address_2: import("zod").ZodString;
114
- company: import("zod").ZodString;
115
115
  province: import("zod").ZodString;
116
116
  }, import("zod/v4/core").$strip>;
117
117
  billing_address: import("zod").ZodObject<{
118
118
  address_1: import("zod").ZodString;
119
119
  city: import("zod").ZodString;
120
+ company: import("zod").ZodString;
120
121
  country_code: import("zod").ZodString;
121
122
  postal_code: import("zod").ZodString;
122
123
  first_name: import("zod").ZodString;
123
124
  last_name: import("zod").ZodString;
124
125
  phone: import("zod").ZodString;
125
126
  address_2: import("zod").ZodString;
126
- company: import("zod").ZodString;
127
127
  province: import("zod").ZodString;
128
128
  }, import("zod/v4/core").$strip>;
129
129
  vat_number: import("zod").ZodString;
@@ -29,25 +29,25 @@ export declare function useCheckout(countryCode: string): {
29
29
  shipping_address: {
30
30
  address_1: string;
31
31
  city: string;
32
+ company: string;
32
33
  country_code: string;
33
34
  postal_code: string;
34
35
  first_name: string;
35
36
  last_name: string;
36
37
  phone: string;
37
38
  address_2: string;
38
- company: string;
39
39
  province: string;
40
40
  };
41
41
  billing_address: {
42
42
  address_1: string;
43
43
  city: string;
44
+ company: string;
44
45
  country_code: string;
45
46
  postal_code: string;
46
47
  first_name: string;
47
48
  last_name: string;
48
49
  phone: string;
49
50
  address_2: string;
50
- company: string;
51
51
  province: string;
52
52
  };
53
53
  vat_number: string;
@@ -60,25 +60,25 @@ export declare function useCheckout(countryCode: string): {
60
60
  shipping_address: {
61
61
  address_1: string;
62
62
  city: string;
63
+ company: string;
63
64
  country_code: string;
64
65
  postal_code: string;
65
66
  first_name: string;
66
67
  last_name: string;
67
68
  phone: string;
68
69
  address_2: string;
69
- company: string;
70
70
  province: string;
71
71
  };
72
72
  billing_address: {
73
73
  address_1: string;
74
74
  city: string;
75
+ company: string;
75
76
  country_code: string;
76
77
  postal_code: string;
77
78
  first_name: string;
78
79
  last_name: string;
79
80
  phone: string;
80
81
  address_2: string;
81
- company: string;
82
82
  province: string;
83
83
  };
84
84
  vat_number: string;
@@ -4,8 +4,8 @@
4
4
  "title": "Customer account",
5
5
  "description": "Sign-in with password or e-mail code, password reset, account verification, address book with UID check, profile, orders, the account workspace layout and the document download route.",
6
6
  "dependencies": [
7
- "@hartl-services/medusa-storefront@^0.2.0",
8
- "@hartl-services/medusa-contracts@^1.1.0",
7
+ "@hartl-services/medusa-storefront@^0.4.0",
8
+ "@hartl-services/medusa-contracts@^1.3.0",
9
9
  "@medusajs/types@^2.19.0",
10
10
  "lucide-react"
11
11
  ],
@@ -4,8 +4,8 @@
4
4
  "title": "Partner portal",
5
5
  "description": "Partner portal: gated registration with UID check, links, coupon codes, commissions with date filter, consignment reports, and its Route Handlers.",
6
6
  "dependencies": [
7
- "@hartl-services/medusa-storefront@^0.2.0",
8
- "@hartl-services/medusa-contracts@^1.1.0",
7
+ "@hartl-services/medusa-storefront@^0.4.0",
8
+ "@hartl-services/medusa-contracts@^1.3.0",
9
9
  "@medusajs/types@^2.19.0",
10
10
  "lucide-react",
11
11
  "react-day-picker"
@@ -134,13 +134,13 @@
134
134
  "path": "registry/items/affiliate/components/affiliate/affiliate-consignment-report-form.tsx",
135
135
  "target": "@components/affiliate/affiliate-consignment-report-form.tsx",
136
136
  "type": "registry:component",
137
- "content": "\"use client\"\n\nimport type { GetConsignmentInventoryResponse, SubmitConsignmentReportRequest } from \"@hartl-services/medusa-contracts/types/affiliate-store\"\n\nimport { fieldControlClassName, fieldLabelClassName } from \"@/components/form/field-styles\"\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { WorkspaceCard } from \"@/components/workspace/workspace-card\"\nimport { useAppForm } from \"@/hooks/use-app-form\"\nimport { useConsignmentReportDefaults } from \"@hartl-services/medusa-storefront/react\"\n\ninterface AffiliateConsignmentReportFormProps {\n inventory: GetConsignmentInventoryResponse\n isPending?: boolean\n onSubmit: (values: SubmitConsignmentReportRequest) => Promise<void>\n}\n\nexport function AffiliateConsignmentReportForm({ inventory, isPending = false, onSubmit }: AffiliateConsignmentReportFormProps) {\n const { defaultValues, submissionIdempotencyKey } = useConsignmentReportDefaults(inventory)\n const form = useAppForm({\n defaultValues,\n onSubmit: async ({ value }) => {\n if (!submissionIdempotencyKey) {\n return\n }\n\n await onSubmit(value)\n },\n })\n\n if (!submissionIdempotencyKey) {\n return (\n <Alert variant=\"destructive\">\n <AlertTitle>Meldung kann nicht vorbereitet werden</AlertTitle>\n <AlertDescription>Dein Browser unterstützt keine sichere Kennung für diese Einreichung. Bitte aktualisiere deinen Browser und versuche es erneut.</AlertDescription>\n </Alert>\n )\n }\n\n return (\n <WorkspaceCard description=\"Trage nur die gezählte Menge je Lagerposition ein. Die erwartete Menge dient als Referenz.\" title=\"Monatsmeldung\">\n <form\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault()\n void form.handleSubmit()\n }}\n >\n <form.AppField mode=\"array\" name=\"counted_batches\">\n {(arrayField) => (\n <fieldset className=\"flex flex-col\">\n <legend className={`${fieldLabelClassName} mb-3`}>Gezählte Lagerbestände</legend>\n {arrayField.state.value.map((line, index) => {\n const quantityId = `counted-quantity-${line.inventory_item_id}-${line.batch_id ?? \"standard\"}`\n\n return (\n <div className=\"grid gap-3 border-b py-4 first:pt-0 last:border-b-0 last:pb-0 sm:grid-cols-[minmax(0,1fr)_auto_10rem] sm:items-end sm:gap-6\" key={`${line.inventory_item_id}-${line.batch_id ?? \"\"}`}>\n <div className=\"flex flex-col gap-1\">\n <p className=\"text-sm font-medium\">Lagerposition {line.inventory_item_id}</p>\n <p className=\"text-sm text-muted-foreground\">{line.batch_id ? `Charge: ${line.batch_id}` : \"Ohne Charge\"}</p>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n Erwartete Menge: <span className=\"font-medium text-foreground\">{inventory.inventory[index]?.expected_quantity ?? 0}</span>\n </p>\n <form.AppField name={`counted_batches[${index}].counted_quantity`}>\n {(field) => {\n const hasErrors = field.state.meta.errors.length > 0\n\n return (\n <div className=\"flex flex-col gap-2\">\n <label className={fieldLabelClassName} htmlFor={quantityId}>\n Gezählte Menge für {line.inventory_item_id}\n </label>\n <Input\n aria-describedby={hasErrors ? `${quantityId}-error` : undefined}\n aria-invalid={hasErrors || undefined}\n className={fieldControlClassName}\n id={quantityId}\n inputMode=\"numeric\"\n min={0}\n onBlur={field.handleBlur}\n onChange={(event) => {\n const nextValue = event.target.value\n field.handleChange(nextValue === \"\" ? 0 : Number(nextValue))\n }}\n step={1}\n type=\"number\"\n value={field.state.value}\n />\n {hasErrors ? (\n <p className=\"text-sm text-destructive\" id={`${quantityId}-error`}>\n Menge muss eine nicht-negative ganze Zahl sein.\n </p>\n ) : null}\n </div>\n )\n }}\n </form.AppField>\n </div>\n )\n })}\n </fieldset>\n )}\n </form.AppField>\n <form.Subscribe\n selector={(state) => ({\n canSubmit: state.canSubmit,\n isSubmitting: state.isSubmitting,\n })}\n >\n {({ canSubmit, isSubmitting }) => (\n <Button className=\"self-start\" disabled={!canSubmit || isSubmitting || isPending} type=\"submit\">\n {isSubmitting || isPending ? \"Monatsmeldung wird eingereicht…\" : \"Monatsmeldung einreichen\"}\n </Button>\n )}\n </form.Subscribe>\n </form>\n </WorkspaceCard>\n )\n}\n"
137
+ "content": "\"use client\"\n\nimport type { GetConsignmentInventoryResponse, SubmitConsignmentReportRequest } from \"@hartl-services/medusa-contracts/types/affiliate-store\"\n\nimport { fieldControlClassName, fieldLabelClassName } from \"@/components/form/field-styles\"\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { WorkspaceCard } from \"@/components/workspace/workspace-card\"\nimport { useAppForm } from \"@/hooks/use-app-form\"\nimport { getConsignmentLineDisplay } from \"@hartl-services/medusa-storefront/core\"\nimport { useConsignmentReportDefaults } from \"@hartl-services/medusa-storefront/react\"\n\ninterface AffiliateConsignmentReportFormProps {\n inventory: GetConsignmentInventoryResponse\n isPending?: boolean\n onSubmit: (values: SubmitConsignmentReportRequest) => Promise<void>\n}\n\nexport function AffiliateConsignmentReportForm({ inventory, isPending = false, onSubmit }: AffiliateConsignmentReportFormProps) {\n const { defaultValues, submissionIdempotencyKey } = useConsignmentReportDefaults(inventory)\n const form = useAppForm({\n defaultValues,\n onSubmit: async ({ value }) => {\n if (!submissionIdempotencyKey) {\n return\n }\n\n await onSubmit(value)\n },\n })\n\n if (!submissionIdempotencyKey) {\n return (\n <Alert variant=\"destructive\">\n <AlertTitle>Meldung kann nicht vorbereitet werden</AlertTitle>\n <AlertDescription>Dein Browser unterstützt keine sichere Kennung für diese Einreichung. Bitte aktualisiere deinen Browser und versuche es erneut.</AlertDescription>\n </Alert>\n )\n }\n\n return (\n <WorkspaceCard description=\"Trage nur die gezählte Menge je Lagerposition ein. Die erwartete Menge dient als Referenz.\" title=\"Monatsmeldung\">\n <form\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault()\n void form.handleSubmit()\n }}\n >\n <form.AppField mode=\"array\" name=\"counted_batches\">\n {(arrayField) => (\n <fieldset className=\"flex flex-col\">\n <legend className={`${fieldLabelClassName} mb-3`}>Gezählte Lagerbestände</legend>\n {arrayField.state.value.map((line, index) => {\n const quantityId = `counted-quantity-${line.inventory_item_id}-${line.batch_id ?? \"standard\"}`\n const display = getConsignmentLineDisplay(inventory.inventory[index] ?? line, \"de-DE\")\n\n return (\n <div className=\"grid gap-3 border-b py-4 first:pt-0 last:border-b-0 last:pb-0 sm:grid-cols-[minmax(0,1fr)_auto_10rem] sm:items-end sm:gap-6\" key={`${line.inventory_item_id}-${line.batch_id ?? \"\"}`}>\n <div className=\"flex flex-col gap-1\">\n <p className=\"text-sm font-medium\">{display.product ?? `Lagerposition ${line.inventory_item_id}`}</p>\n <p className=\"text-sm text-muted-foreground\">{display.batch ? `Charge ${display.batch}${display.expiryDate ? ` · MHD ${display.expiryDate}` : \"\"}` : \"Ohne Charge\"}</p>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n Erwartete Menge: <span className=\"font-medium text-foreground\">{inventory.inventory[index]?.expected_quantity ?? 0}</span>\n </p>\n <form.AppField name={`counted_batches[${index}].counted_quantity`}>\n {(field) => {\n const hasErrors = field.state.meta.errors.length > 0\n\n return (\n <div className=\"flex flex-col gap-2\">\n <label className={fieldLabelClassName} htmlFor={quantityId}>\n Gezählte Menge für {display.product ?? line.inventory_item_id}\n </label>\n <Input\n aria-describedby={hasErrors ? `${quantityId}-error` : undefined}\n aria-invalid={hasErrors || undefined}\n className={fieldControlClassName}\n id={quantityId}\n inputMode=\"numeric\"\n min={0}\n onBlur={field.handleBlur}\n onChange={(event) => {\n const nextValue = event.target.value\n field.handleChange(nextValue === \"\" ? 0 : Number(nextValue))\n }}\n step={1}\n type=\"number\"\n value={field.state.value}\n />\n {hasErrors ? (\n <p className=\"text-sm text-destructive\" id={`${quantityId}-error`}>\n Menge muss eine nicht-negative ganze Zahl sein.\n </p>\n ) : null}\n </div>\n )\n }}\n </form.AppField>\n </div>\n )\n })}\n </fieldset>\n )}\n </form.AppField>\n <form.Subscribe\n selector={(state) => ({\n canSubmit: state.canSubmit,\n isSubmitting: state.isSubmitting,\n })}\n >\n {({ canSubmit, isSubmitting }) => (\n <Button className=\"self-start\" disabled={!canSubmit || isSubmitting || isPending} type=\"submit\">\n {isSubmitting || isPending ? \"Monatsmeldung wird eingereicht…\" : \"Monatsmeldung einreichen\"}\n </Button>\n )}\n </form.Subscribe>\n </form>\n </WorkspaceCard>\n )\n}\n"
138
138
  },
139
139
  {
140
140
  "path": "registry/items/affiliate/components/affiliate/affiliate-consignment-reports.tsx",
141
141
  "target": "@components/affiliate/affiliate-consignment-reports.tsx",
142
142
  "type": "registry:component",
143
- "content": "import type { StoreConsignmentReport } from \"@hartl-services/medusa-contracts/types/affiliate-store\"\n\nimport { Badge } from \"@/components/ui/badge\"\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { WorkspaceCard } from \"@/components/workspace/workspace-card\"\n\ntype ReportStatus = StoreConsignmentReport[\"status\"]\n\nconst reportStatusLabels: Record<ReportStatus, string> = {\n draft: \"Entwurf\",\n submitted: \"Eingereicht\",\n approved: \"Genehmigt\",\n rejected: \"Abgelehnt\",\n}\n\nconst reportStatusVariants: Record<ReportStatus, \"default\" | \"destructive\" | \"outline\"> = {\n draft: \"outline\",\n submitted: \"outline\",\n approved: \"default\",\n rejected: \"destructive\",\n}\n\ninterface AffiliateConsignmentReportsProps {\n reports: StoreConsignmentReport[]\n}\n\nfunction formatDate(value: string | null) {\n if (!value) {\n return \"Noch nicht eingereicht\"\n }\n\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) {\n return value\n }\n\n return new Intl.DateTimeFormat(\"de-DE\", {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n }).format(date)\n}\n\nexport function AffiliateConsignmentReports({ reports }: AffiliateConsignmentReportsProps) {\n if (reports.length === 0) {\n return <WorkspaceCard description=\"Für dein Kommissionslager liegen noch keine Monatsmeldungen vor.\" title=\"Bisherige Meldungen\" />\n }\n\n return (\n <section aria-labelledby=\"consignment-reports-heading\" className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2\">\n <h2 className=\"text-lg font-medium\" id=\"consignment-reports-heading\">\n Bisherige Meldungen\n </h2>\n <p className=\"text-sm text-muted-foreground\">Status und die serverseitig aufgelösten Lagerpositionen deiner Monatsmeldungen.</p>\n </div>\n <div className=\"flex flex-col gap-4\">\n {reports.map((report) => (\n <Card key={report.id}>\n <CardContent>\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex flex-col gap-2\">\n <h3 className=\"font-medium\">\n Zeitraum {report.period_start} bis {report.period_end}\n </h3>\n <p className=\"text-sm text-muted-foreground\">Eingereicht: {formatDate(report.submitted_at)}</p>\n </div>\n <Badge variant={reportStatusVariants[report.status]}>{reportStatusLabels[report.status]}</Badge>\n </div>\n <ul aria-label={`Lagerpositionen für ${report.period_start}`} className=\"flex flex-col\">\n {report.lines.map((line, index) => (\n <li key={line.id}>\n {index > 0 ? <Separator /> : null}\n <div className=\"flex flex-wrap items-center justify-between gap-x-6 gap-y-2 py-3 text-sm\">\n <div className=\"flex flex-col gap-1\">\n <p className=\"font-medium\">Lagerposition {line.inventory_item_id}</p>\n <p className=\"text-muted-foreground\">\n {line.batch_id ? `Charge: ${line.batch_id}` : \"Ohne Charge\"} · Erwartet: {line.expected_quantity} · Gezählt: {line.counted_quantity}\n </p>\n </div>\n {line.requires_admin_clarification ? <Badge variant=\"destructive\">Klärung erforderlich</Badge> : null}\n </div>\n </li>\n ))}\n </ul>\n </CardContent>\n </Card>\n ))}\n </div>\n </section>\n )\n}\n"
143
+ "content": "import type { StoreConsignmentReport } from \"@hartl-services/medusa-contracts/types/affiliate-store\"\nimport { getConsignmentLineDisplay } from \"@hartl-services/medusa-storefront/core\"\n\nimport { Badge } from \"@/components/ui/badge\"\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { WorkspaceCard } from \"@/components/workspace/workspace-card\"\n\ntype ReportStatus = StoreConsignmentReport[\"status\"]\n\nconst reportStatusLabels: Record<ReportStatus, string> = {\n draft: \"Entwurf\",\n submitted: \"Eingereicht\",\n approved: \"Genehmigt\",\n rejected: \"Abgelehnt\",\n}\n\nconst reportStatusVariants: Record<ReportStatus, \"default\" | \"destructive\" | \"outline\"> = {\n draft: \"outline\",\n submitted: \"outline\",\n approved: \"default\",\n rejected: \"destructive\",\n}\n\ninterface AffiliateConsignmentReportsProps {\n reports: StoreConsignmentReport[]\n}\n\nfunction formatDate(value: string | null) {\n if (!value) {\n return \"Noch nicht eingereicht\"\n }\n\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) {\n return value\n }\n\n return new Intl.DateTimeFormat(\"de-DE\", {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n }).format(date)\n}\n\nexport function AffiliateConsignmentReports({ reports }: AffiliateConsignmentReportsProps) {\n if (reports.length === 0) {\n return <WorkspaceCard description=\"Für dein Kommissionslager liegen noch keine Monatsmeldungen vor.\" title=\"Bisherige Meldungen\" />\n }\n\n return (\n <section aria-labelledby=\"consignment-reports-heading\" className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2\">\n <h2 className=\"text-lg font-medium\" id=\"consignment-reports-heading\">\n Bisherige Meldungen\n </h2>\n <p className=\"text-sm text-muted-foreground\">Status und die serverseitig aufgelösten Lagerpositionen deiner Monatsmeldungen.</p>\n </div>\n <div className=\"flex flex-col gap-4\">\n {reports.map((report) => (\n <Card key={report.id}>\n <CardContent>\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex flex-col gap-2\">\n <h3 className=\"font-medium\">\n Zeitraum {report.period_start} bis {report.period_end}\n </h3>\n <p className=\"text-sm text-muted-foreground\">Eingereicht: {formatDate(report.submitted_at)}</p>\n </div>\n <Badge variant={reportStatusVariants[report.status]}>{reportStatusLabels[report.status]}</Badge>\n </div>\n <ul aria-label={`Lagerpositionen für ${report.period_start}`} className=\"flex flex-col\">\n {report.lines.map((line, index) => {\n const display = getConsignmentLineDisplay(line, \"de-DE\")\n\n return (\n <li key={line.id}>\n {index > 0 ? <Separator /> : null}\n <div className=\"flex flex-wrap items-center justify-between gap-x-6 gap-y-2 py-3 text-sm\">\n <div className=\"flex flex-col gap-1\">\n <p className=\"font-medium\">{display.product ?? `Lagerposition ${line.inventory_item_id}`}</p>\n <p className=\"text-muted-foreground\">\n {display.batch ? `Charge ${display.batch}${display.expiryDate ? ` · MHD ${display.expiryDate}` : \"\"}` : \"Ohne Charge\"} · Erwartet: {line.expected_quantity} · Gezählt: {line.counted_quantity}\n </p>\n </div>\n {line.requires_admin_clarification ? <Badge variant=\"destructive\">Klärung erforderlich</Badge> : null}\n </div>\n </li>\n )\n })}\n </ul>\n </CardContent>\n </Card>\n ))}\n </div>\n </section>\n )\n}\n"
144
144
  },
145
145
  {
146
146
  "path": "registry/items/affiliate/components/affiliate/affiliate-coupon-code-actions.tsx",
@@ -4,7 +4,7 @@
4
4
  "title": "Cart",
5
5
  "description": "Cart widget with popover, line items, totals, cart page, add-to-cart button, variant picker and product price.",
6
6
  "dependencies": [
7
- "@hartl-services/medusa-storefront@^0.2.0",
7
+ "@hartl-services/medusa-storefront@^0.4.0",
8
8
  "@medusajs/types@^2.19.0",
9
9
  "lucide-react"
10
10
  ],
@@ -4,7 +4,7 @@
4
4
  "title": "Checkout",
5
5
  "description": "Checkout page (address, login prompt, shipping and Stripe payment, coupon, UID status) and the order confirmation page.",
6
6
  "dependencies": [
7
- "@hartl-services/medusa-storefront@^0.2.0",
7
+ "@hartl-services/medusa-storefront@^0.4.0",
8
8
  "@medusajs/types@^2.19.0",
9
9
  "@stripe/react-stripe-js@^5.6.1",
10
10
  "@stripe/stripe-js@^8.11.0",
@@ -4,7 +4,7 @@
4
4
  "title": "Storefront form kit",
5
5
  "description": "The shop's TanStack form hook (useAppForm) with unstyled field components; the package's hooks hand their formOptions to it.",
6
6
  "dependencies": [
7
- "@hartl-services/medusa-storefront@^0.2.0",
7
+ "@hartl-services/medusa-storefront@^0.4.0",
8
8
  "@tanstack/react-form@^1.33.5",
9
9
  "input-otp",
10
10
  "lucide-react"
@@ -7,8 +7,8 @@
7
7
  "title": "Storefront setup",
8
8
  "description": "Wires the storefront package into a Next shop: server and browser instances, the Server Action wrappers, texts, error reporting and the provider root for the [countryCode] layout.",
9
9
  "dependencies": [
10
- "@hartl-services/medusa-storefront@^0.2.0",
11
- "@hartl-services/medusa-contracts@^1.1.0",
10
+ "@hartl-services/medusa-storefront@^0.4.0",
11
+ "@hartl-services/medusa-contracts@^1.3.0",
12
12
  "@medusajs/framework@^2.19.0",
13
13
  "@medusajs/js-sdk@^2.19.0",
14
14
  "@medusajs/types@^2.19.0",
@@ -73,7 +73,7 @@
73
73
  "title": "Storefront form kit",
74
74
  "description": "The shop's TanStack form hook (useAppForm) with unstyled field components; the package's hooks hand their formOptions to it.",
75
75
  "dependencies": [
76
- "@hartl-services/medusa-storefront@^0.2.0",
76
+ "@hartl-services/medusa-storefront@^0.4.0",
77
77
  "@tanstack/react-form@^1.33.5",
78
78
  "input-otp",
79
79
  "lucide-react"
@@ -197,7 +197,7 @@
197
197
  "title": "Cart",
198
198
  "description": "Cart widget with popover, line items, totals, cart page, add-to-cart button, variant picker and product price.",
199
199
  "dependencies": [
200
- "@hartl-services/medusa-storefront@^0.2.0",
200
+ "@hartl-services/medusa-storefront@^0.4.0",
201
201
  "@medusajs/types@^2.19.0",
202
202
  "lucide-react"
203
203
  ],
@@ -306,8 +306,8 @@
306
306
  "title": "Customer account",
307
307
  "description": "Sign-in with password or e-mail code, password reset, account verification, address book with UID check, profile, orders, the account workspace layout and the document download route.",
308
308
  "dependencies": [
309
- "@hartl-services/medusa-storefront@^0.2.0",
310
- "@hartl-services/medusa-contracts@^1.1.0",
309
+ "@hartl-services/medusa-storefront@^0.4.0",
310
+ "@hartl-services/medusa-contracts@^1.3.0",
311
311
  "@medusajs/types@^2.19.0",
312
312
  "lucide-react"
313
313
  ],
@@ -516,7 +516,7 @@
516
516
  "title": "Checkout",
517
517
  "description": "Checkout page (address, login prompt, shipping and Stripe payment, coupon, UID status) and the order confirmation page.",
518
518
  "dependencies": [
519
- "@hartl-services/medusa-storefront@^0.2.0",
519
+ "@hartl-services/medusa-storefront@^0.4.0",
520
520
  "@medusajs/types@^2.19.0",
521
521
  "@stripe/react-stripe-js@^5.6.1",
522
522
  "@stripe/stripe-js@^8.11.0",
@@ -661,8 +661,8 @@
661
661
  "title": "Partner portal",
662
662
  "description": "Partner portal: gated registration with UID check, links, coupon codes, commissions with date filter, consignment reports, and its Route Handlers.",
663
663
  "dependencies": [
664
- "@hartl-services/medusa-storefront@^0.2.0",
665
- "@hartl-services/medusa-contracts@^1.1.0",
664
+ "@hartl-services/medusa-storefront@^0.4.0",
665
+ "@hartl-services/medusa-contracts@^1.3.0",
666
666
  "@medusajs/types@^2.19.0",
667
667
  "lucide-react",
668
668
  "react-day-picker"
@@ -4,8 +4,8 @@
4
4
  "title": "Storefront setup",
5
5
  "description": "Wires the storefront package into a Next shop: server and browser instances, the Server Action wrappers, texts, error reporting and the provider root for the [countryCode] layout.",
6
6
  "dependencies": [
7
- "@hartl-services/medusa-storefront@^0.2.0",
8
- "@hartl-services/medusa-contracts@^1.1.0",
7
+ "@hartl-services/medusa-storefront@^0.4.0",
8
+ "@hartl-services/medusa-contracts@^1.3.0",
9
9
  "@medusajs/framework@^2.19.0",
10
10
  "@medusajs/js-sdk@^2.19.0",
11
11
  "@medusajs/types@^2.19.0",
@@ -34,7 +34,7 @@
34
34
  "path": "registry/items/setup/lib/storefront/actions.ts",
35
35
  "target": "@lib/storefront/actions.ts",
36
36
  "type": "registry:lib",
37
- "content": "\"use server\"\n\n// One Server Action per storefront server function: Next only accepts \"use server\"\n// functions defined in the app. The browser side gets them through lib/storefront/client.ts.\n\nimport { storefront } from \"@/lib/storefront/server\"\n\nexport async function getActiveCart(...args: Parameters<typeof storefront.cart.getActiveCart>) {\n return await storefront.cart.getActiveCart(...args)\n}\n\nexport async function applyCustomerDefaultsToActiveCart(...args: Parameters<typeof storefront.cart.applyCustomerDefaultsToActiveCart>) {\n return await storefront.cart.applyCustomerDefaultsToActiveCart(...args)\n}\n\nexport async function syncCartId(...args: Parameters<typeof storefront.cart.syncCartId>) {\n return await storefront.cart.syncCartId(...args)\n}\n\nexport async function clearCartId(...args: Parameters<typeof storefront.cart.clearCartId>) {\n return await storefront.cart.clearCartId(...args)\n}\n\nexport async function discardActiveCart(...args: Parameters<typeof storefront.cart.discardActiveCart>) {\n return await storefront.cart.discardActiveCart(...args)\n}\n\nexport async function retrieveCustomer(...args: Parameters<typeof storefront.customer.retrieveCustomer>) {\n return await storefront.customer.retrieveCustomer(...args)\n}\n\nexport async function loginCustomer(...args: Parameters<typeof storefront.customer.loginCustomer>) {\n return await storefront.customer.loginCustomer(...args)\n}\n\nexport async function checkCustomerVerification(...args: Parameters<typeof storefront.customer.checkCustomerVerification>) {\n return await storefront.customer.checkCustomerVerification(...args)\n}\n\nexport async function registerCustomer(...args: Parameters<typeof storefront.customer.registerCustomer>) {\n return await storefront.customer.registerCustomer(...args)\n}\n\nexport async function registerCustomerWithProfile(...args: Parameters<typeof storefront.customer.registerCustomerWithProfile>) {\n return await storefront.customer.registerCustomerWithProfile(...args)\n}\n\nexport async function requestLoginCode(...args: Parameters<typeof storefront.customer.requestLoginCode>) {\n return await storefront.customer.requestLoginCode(...args)\n}\n\nexport async function loginWithCode(...args: Parameters<typeof storefront.customer.loginWithCode>) {\n return await storefront.customer.loginWithCode(...args)\n}\n\nexport async function requestCustomerPasswordReset(...args: Parameters<typeof storefront.customer.requestCustomerPasswordReset>) {\n return await storefront.customer.requestCustomerPasswordReset(...args)\n}\n\nexport async function resetCustomerPassword(...args: Parameters<typeof storefront.customer.resetCustomerPassword>) {\n return await storefront.customer.resetCustomerPassword(...args)\n}\n\nexport async function confirmCustomerEmail(...args: Parameters<typeof storefront.customer.confirmCustomerEmail>) {\n return await storefront.customer.confirmCustomerEmail(...args)\n}\n\nexport async function resendCustomerVerification(...args: Parameters<typeof storefront.customer.resendCustomerVerification>) {\n return await storefront.customer.resendCustomerVerification(...args)\n}\n\nexport async function fetchCustomerPasswordPolicy(...args: Parameters<typeof storefront.customer.fetchCustomerPasswordPolicy>) {\n return await storefront.customer.fetchCustomerPasswordPolicy(...args)\n}\n\nexport async function updateCustomer(...args: Parameters<typeof storefront.customer.updateCustomer>) {\n return await storefront.customer.updateCustomer(...args)\n}\n\nexport async function createCustomerAddress(...args: Parameters<typeof storefront.customer.createCustomerAddress>) {\n return await storefront.customer.createCustomerAddress(...args)\n}\n\nexport async function saveFirstCheckoutAddresses(...args: Parameters<typeof storefront.customer.saveFirstCheckoutAddresses>) {\n return await storefront.customer.saveFirstCheckoutAddresses(...args)\n}\n\nexport async function updateCustomerAddress(...args: Parameters<typeof storefront.customer.updateCustomerAddress>) {\n return await storefront.customer.updateCustomerAddress(...args)\n}\n\nexport async function deleteCustomerAddress(...args: Parameters<typeof storefront.customer.deleteCustomerAddress>) {\n return await storefront.customer.deleteCustomerAddress(...args)\n}\n\nexport async function logoutCustomer(...args: Parameters<typeof storefront.customer.logoutCustomer>) {\n return await storefront.customer.logoutCustomer(...args)\n}\n\nexport async function listShippingOptions(...args: Parameters<typeof storefront.checkout.listShippingOptions>) {\n return await storefront.checkout.listShippingOptions(...args)\n}\n\nexport async function listPaymentProviders(...args: Parameters<typeof storefront.checkout.listPaymentProviders>) {\n return await storefront.checkout.listPaymentProviders(...args)\n}\n\nexport async function listRegions(...args: Parameters<typeof storefront.region.listRegions>) {\n return await storefront.region.listRegions(...args)\n}\n\nexport async function fetchPluginFeatures(...args: Parameters<typeof storefront.pluginFeatures.fetchPluginFeatures>) {\n return await storefront.pluginFeatures.fetchPluginFeatures(...args)\n}\n\nexport async function checkCartTaxIdentification(...args: Parameters<typeof storefront.taxCompliance.checkCartTaxIdentification>) {\n return await storefront.taxCompliance.checkCartTaxIdentification(...args)\n}\n\nexport async function listCustomerTaxIdentifications(...args: Parameters<typeof storefront.taxCompliance.listCustomerTaxIdentifications>) {\n return await storefront.taxCompliance.listCustomerTaxIdentifications(...args)\n}\n\nexport async function verifyCustomerAddressTaxIdentification(...args: Parameters<typeof storefront.taxCompliance.verifyCustomerAddressTaxIdentification>) {\n return await storefront.taxCompliance.verifyCustomerAddressTaxIdentification(...args)\n}\n\nexport async function submitAffiliateApplication(...args: Parameters<typeof storefront.affiliate.submitAffiliateApplication>) {\n return await storefront.affiliate.submitAffiliateApplication(...args)\n}\n\nexport async function applyAffiliateOwnOrderPromotion(...args: Parameters<typeof storefront.affiliate.applyAffiliateOwnOrderPromotion>) {\n return await storefront.affiliate.applyAffiliateOwnOrderPromotion(...args)\n}\n\nexport async function createAffiliateSlug(...args: Parameters<typeof storefront.affiliate.createAffiliateSlug>) {\n return await storefront.affiliate.createAffiliateSlug(...args)\n}\n\nexport async function updateAffiliateSlug(...args: Parameters<typeof storefront.affiliate.updateAffiliateSlug>) {\n return await storefront.affiliate.updateAffiliateSlug(...args)\n}\n\nexport async function createAffiliateCouponCode(...args: Parameters<typeof storefront.affiliate.createAffiliateCouponCode>) {\n return await storefront.affiliate.createAffiliateCouponCode(...args)\n}\n\nexport async function rotateAffiliateCouponCode(...args: Parameters<typeof storefront.affiliate.rotateAffiliateCouponCode>) {\n return await storefront.affiliate.rotateAffiliateCouponCode(...args)\n}\n\nexport async function revokeAffiliateCouponCode(...args: Parameters<typeof storefront.affiliate.revokeAffiliateCouponCode>) {\n return await storefront.affiliate.revokeAffiliateCouponCode(...args)\n}\n\nexport async function createConsignmentReport(...args: Parameters<typeof storefront.affiliate.createConsignmentReport>) {\n return await storefront.affiliate.createConsignmentReport(...args)\n}\n\nexport async function listOrders(...args: Parameters<typeof storefront.orders.listOrders>) {\n return await storefront.orders.listOrders(...args)\n}\n\nexport async function getOrder(...args: Parameters<typeof storefront.orders.getOrder>) {\n return await storefront.orders.getOrder(...args)\n}\n\nexport async function listDocuments(...args: Parameters<typeof storefront.documents.listDocuments>) {\n return await storefront.documents.listDocuments(...args)\n}\n"
37
+ "content": "\"use server\"\n\n// One Server Action per storefront server function: Next only accepts \"use server\"\n// functions defined in the app. The browser side gets them through lib/storefront/client.ts.\n\nimport { storefront } from \"@/lib/storefront/server\"\n\nexport async function getActiveCart(...args: Parameters<typeof storefront.cart.getActiveCart>) {\n return await storefront.cart.getActiveCart(...args)\n}\n\nexport async function applyCustomerDefaultsToActiveCart(...args: Parameters<typeof storefront.cart.applyCustomerDefaultsToActiveCart>) {\n return await storefront.cart.applyCustomerDefaultsToActiveCart(...args)\n}\n\nexport async function syncCartId(...args: Parameters<typeof storefront.cart.syncCartId>) {\n return await storefront.cart.syncCartId(...args)\n}\n\nexport async function clearCartId(...args: Parameters<typeof storefront.cart.clearCartId>) {\n return await storefront.cart.clearCartId(...args)\n}\n\nexport async function discardActiveCart(...args: Parameters<typeof storefront.cart.discardActiveCart>) {\n return await storefront.cart.discardActiveCart(...args)\n}\n\nexport async function retrieveCustomer(...args: Parameters<typeof storefront.customer.retrieveCustomer>) {\n return await storefront.customer.retrieveCustomer(...args)\n}\n\nexport async function loginCustomer(...args: Parameters<typeof storefront.customer.loginCustomer>) {\n return await storefront.customer.loginCustomer(...args)\n}\n\nexport async function checkCustomerVerification(...args: Parameters<typeof storefront.customer.checkCustomerVerification>) {\n return await storefront.customer.checkCustomerVerification(...args)\n}\n\nexport async function registerCustomer(...args: Parameters<typeof storefront.customer.registerCustomer>) {\n return await storefront.customer.registerCustomer(...args)\n}\n\nexport async function registerCustomerWithProfile(...args: Parameters<typeof storefront.customer.registerCustomerWithProfile>) {\n return await storefront.customer.registerCustomerWithProfile(...args)\n}\n\nexport async function registerPartnerWithProfile(...args: Parameters<typeof storefront.customer.registerPartnerWithProfile>) {\n return await storefront.customer.registerPartnerWithProfile(...args)\n}\n\nexport async function requestLoginCode(...args: Parameters<typeof storefront.customer.requestLoginCode>) {\n return await storefront.customer.requestLoginCode(...args)\n}\n\nexport async function loginWithCode(...args: Parameters<typeof storefront.customer.loginWithCode>) {\n return await storefront.customer.loginWithCode(...args)\n}\n\nexport async function requestCustomerPasswordReset(...args: Parameters<typeof storefront.customer.requestCustomerPasswordReset>) {\n return await storefront.customer.requestCustomerPasswordReset(...args)\n}\n\nexport async function resetCustomerPassword(...args: Parameters<typeof storefront.customer.resetCustomerPassword>) {\n return await storefront.customer.resetCustomerPassword(...args)\n}\n\nexport async function confirmCustomerEmail(...args: Parameters<typeof storefront.customer.confirmCustomerEmail>) {\n return await storefront.customer.confirmCustomerEmail(...args)\n}\n\nexport async function resendCustomerVerification(...args: Parameters<typeof storefront.customer.resendCustomerVerification>) {\n return await storefront.customer.resendCustomerVerification(...args)\n}\n\nexport async function fetchCustomerPasswordPolicy(...args: Parameters<typeof storefront.customer.fetchCustomerPasswordPolicy>) {\n return await storefront.customer.fetchCustomerPasswordPolicy(...args)\n}\n\nexport async function updateCustomer(...args: Parameters<typeof storefront.customer.updateCustomer>) {\n return await storefront.customer.updateCustomer(...args)\n}\n\nexport async function createCustomerAddress(...args: Parameters<typeof storefront.customer.createCustomerAddress>) {\n return await storefront.customer.createCustomerAddress(...args)\n}\n\nexport async function saveFirstCheckoutAddresses(...args: Parameters<typeof storefront.customer.saveFirstCheckoutAddresses>) {\n return await storefront.customer.saveFirstCheckoutAddresses(...args)\n}\n\nexport async function updateCustomerAddress(...args: Parameters<typeof storefront.customer.updateCustomerAddress>) {\n return await storefront.customer.updateCustomerAddress(...args)\n}\n\nexport async function deleteCustomerAddress(...args: Parameters<typeof storefront.customer.deleteCustomerAddress>) {\n return await storefront.customer.deleteCustomerAddress(...args)\n}\n\nexport async function logoutCustomer(...args: Parameters<typeof storefront.customer.logoutCustomer>) {\n return await storefront.customer.logoutCustomer(...args)\n}\n\nexport async function listShippingOptions(...args: Parameters<typeof storefront.checkout.listShippingOptions>) {\n return await storefront.checkout.listShippingOptions(...args)\n}\n\nexport async function listPaymentProviders(...args: Parameters<typeof storefront.checkout.listPaymentProviders>) {\n return await storefront.checkout.listPaymentProviders(...args)\n}\n\nexport async function listRegions(...args: Parameters<typeof storefront.region.listRegions>) {\n return await storefront.region.listRegions(...args)\n}\n\nexport async function fetchPluginFeatures(...args: Parameters<typeof storefront.pluginFeatures.fetchPluginFeatures>) {\n return await storefront.pluginFeatures.fetchPluginFeatures(...args)\n}\n\nexport async function checkCartTaxIdentification(...args: Parameters<typeof storefront.taxCompliance.checkCartTaxIdentification>) {\n return await storefront.taxCompliance.checkCartTaxIdentification(...args)\n}\n\nexport async function listCustomerTaxIdentifications(...args: Parameters<typeof storefront.taxCompliance.listCustomerTaxIdentifications>) {\n return await storefront.taxCompliance.listCustomerTaxIdentifications(...args)\n}\n\nexport async function verifyCustomerAddressTaxIdentification(...args: Parameters<typeof storefront.taxCompliance.verifyCustomerAddressTaxIdentification>) {\n return await storefront.taxCompliance.verifyCustomerAddressTaxIdentification(...args)\n}\n\nexport async function submitAffiliateApplication(...args: Parameters<typeof storefront.affiliate.submitAffiliateApplication>) {\n return await storefront.affiliate.submitAffiliateApplication(...args)\n}\n\nexport async function applyAffiliateOwnOrderPromotion(...args: Parameters<typeof storefront.affiliate.applyAffiliateOwnOrderPromotion>) {\n return await storefront.affiliate.applyAffiliateOwnOrderPromotion(...args)\n}\n\nexport async function createAffiliateSlug(...args: Parameters<typeof storefront.affiliate.createAffiliateSlug>) {\n return await storefront.affiliate.createAffiliateSlug(...args)\n}\n\nexport async function updateAffiliateSlug(...args: Parameters<typeof storefront.affiliate.updateAffiliateSlug>) {\n return await storefront.affiliate.updateAffiliateSlug(...args)\n}\n\nexport async function createAffiliateCouponCode(...args: Parameters<typeof storefront.affiliate.createAffiliateCouponCode>) {\n return await storefront.affiliate.createAffiliateCouponCode(...args)\n}\n\nexport async function rotateAffiliateCouponCode(...args: Parameters<typeof storefront.affiliate.rotateAffiliateCouponCode>) {\n return await storefront.affiliate.rotateAffiliateCouponCode(...args)\n}\n\nexport async function revokeAffiliateCouponCode(...args: Parameters<typeof storefront.affiliate.revokeAffiliateCouponCode>) {\n return await storefront.affiliate.revokeAffiliateCouponCode(...args)\n}\n\nexport async function createConsignmentReport(...args: Parameters<typeof storefront.affiliate.createConsignmentReport>) {\n return await storefront.affiliate.createConsignmentReport(...args)\n}\n\nexport async function listOrders(...args: Parameters<typeof storefront.orders.listOrders>) {\n return await storefront.orders.listOrders(...args)\n}\n\nexport async function getOrder(...args: Parameters<typeof storefront.orders.getOrder>) {\n return await storefront.orders.getOrder(...args)\n}\n\nexport async function listDocuments(...args: Parameters<typeof storefront.documents.listDocuments>) {\n return await storefront.documents.listDocuments(...args)\n}\n"
38
38
  },
39
39
  {
40
40
  "path": "registry/items/setup/lib/storefront/client.ts",
@@ -1,4 +1,4 @@
1
- import type { ApplyOwnOrderPromotionResponse, CreatePartnerPortalSlugRequest, CreateStoreAffiliateCouponCodeRequest, RotateStoreAffiliateCouponCodeRequest, SubmitAffiliateApplicationRequest, SubmitConsignmentReportRequest, UpdatePartnerPortalSlugRequest } from "@hartl-services/medusa-contracts/types/affiliate-store";
1
+ import type { ApplyOwnOrderPromotionResponse, CreatePartnerPortalSlugRequest, CreateStoreAffiliateCouponCodeRequest, RotateStoreAffiliateCouponCodeRequest, StoreAffiliatePortalContextResponse, SubmitAffiliateApplicationRequest, SubmitConsignmentReportRequest, UpdatePartnerPortalSlugRequest } from "@hartl-services/medusa-contracts/types/affiliate-store";
2
2
  import type { MedusaActionResult } from "../core/action-result.js";
3
3
  import type { ServerContext } from "./context.js";
4
4
  import type { ServerPluginFeatures } from "./plugin-features.js";
@@ -12,6 +12,9 @@ export interface AffiliateCouponRotationInput {
12
12
  command: RotateStoreAffiliateCouponCodeRequest;
13
13
  }
14
14
  export declare function createAffiliate(context: ServerContext, pluginFeatures: ServerPluginFeatures, runMedusaAction: RunMedusaAction): {
15
+ loadPartnerPortalState: () => Promise<StoreAffiliatePortalContextResponse["portal_state"] | null>;
16
+ /** Whether the logged-in customer is an approved, active partner (fail-closed). */
17
+ isActivePartner(): Promise<boolean>;
15
18
  submitAffiliateApplication(data: SubmitAffiliateApplicationRequest): Promise<MedusaActionResult<{
16
19
  application: {
17
20
  id: string;
@@ -105,6 +108,10 @@ export declare function createAffiliate(context: ServerContext, pluginFeatures:
105
108
  resolved_unit_price: number | null;
106
109
  discounted_line_total: number | null;
107
110
  currency_code: string | null;
111
+ product_title: string | null;
112
+ variant_title: string | null;
113
+ batch_name: string | null;
114
+ batch_expiry_date: string | null;
108
115
  }[];
109
116
  };
110
117
  draft_order: {
@@ -1,3 +1,4 @@
1
+ import { cache } from "react";
1
2
  import { affiliateStorePaths } from "@hartl-services/medusa-contracts/paths/affiliate";
2
3
  import { isPluginFeatureEnabled } from "../core/plugin-features.js";
3
4
  import { createMedusaErrorResponse } from "./route-response.js";
@@ -19,7 +20,35 @@ export function createAffiliate(context, pluginFeatures, runMedusaAction) {
19
20
  return medusaErrorResponse(error);
20
21
  }
21
22
  }
23
+ /**
24
+ * The logged-in customer's partner state for server rendering (e.g. a
25
+ * therapist view): null when logged out, while the partner portal is off,
26
+ * or when the backend cannot answer - every partner view then stays hidden.
27
+ * Deduplicated per request via React cache().
28
+ */
29
+ const loadPartnerPortalState = cache(async () => {
30
+ if (!(await context.session.getMedusaAuthToken())) {
31
+ return null;
32
+ }
33
+ if (!isPluginFeatureEnabled(await pluginFeatures.loadPluginFeatures(), "affiliate", "partner_portal")) {
34
+ return null;
35
+ }
36
+ try {
37
+ const sdk = await customerClient();
38
+ const response = await sdk.client.fetch(affiliateStorePaths.get.me, { method: "GET" });
39
+ return response.portal_state;
40
+ }
41
+ catch (error) {
42
+ context.reportError(error, { area: "affiliate", context: { step: "portal_state" } });
43
+ return null;
44
+ }
45
+ });
22
46
  return {
47
+ loadPartnerPortalState,
48
+ /** Whether the logged-in customer is an approved, active partner (fail-closed). */
49
+ async isActivePartner() {
50
+ return (await loadPartnerPortalState()) === "active";
51
+ },
23
52
  async submitAffiliateApplication(data) {
24
53
  return runMedusaAction(async () => {
25
54
  const sdk = await customerClient();
@@ -9,8 +9,13 @@ export interface CommerceProductListInput {
9
9
  /** Medusa's product search (title, description, handle, ...). */
10
10
  q?: string;
11
11
  }
12
- /** Medusa's half of a catalog: products priced for a country, categories and collections. */
13
- export declare function createCatalog(context: ServerContext, region: ServerRegion): {
12
+ /**
13
+ * Medusa's half of a catalog: products priced for a country, categories and
14
+ * collections. Partner-only products and categories (see
15
+ * `isPartnerOnlyProduct`) are left out unless `isActivePartner` says the
16
+ * visitor is an active partner; the backend refuses them at checkout anyway.
17
+ */
18
+ export declare function createCatalog(context: ServerContext, region: ServerRegion, isActivePartner?: () => Promise<boolean>): {
14
19
  listCommerceProducts({ categoryId, collectionId, countryCode, handles, q }: CommerceProductListInput): Promise<HttpTypes.StoreProduct[] | null>;
15
20
  commerceProductByHandle({ countryCode, handle }: {
16
21
  countryCode: string;
@@ -19,7 +24,8 @@ export declare function createCatalog(context: ServerContext, region: ServerRegi
19
24
  /**
20
25
  * The top-level categories with all descendants (e.g. "Anwendungsbereiche"
21
26
  * and "Inhaltsstoffe" with their entries) - the filter trees of a product
22
- * list; filter with `filterProductsByCategories` from /core.
27
+ * list; filter with `filterProductsByCategories` from /core. Partner-only
28
+ * categories only for an active partner.
23
29
  */
24
30
  listCategoryTrees(): Promise<HttpTypes.StoreProductCategory[]>;
25
31
  categoryByPath(categoryPath: string[]): Promise<HttpTypes.StoreProductCategory | null>;
@@ -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,6 +20,29 @@ 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
47
  async listCommerceProducts({ categoryId, collectionId, countryCode, handles, q }) {
18
48
  const pricingContext = await getPricingContext(countryCode);
@@ -29,7 +59,7 @@ export function createCatalog(context, region) {
29
59
  fields: PRODUCT_FIELDS,
30
60
  limit: catalogFetchLimit,
31
61
  });
32
- return products;
62
+ return await visibleProducts(products);
33
63
  },
34
64
  async commerceProductByHandle({ countryCode, handle }) {
35
65
  const pricingContext = await getPricingContext(countryCode);
@@ -43,22 +73,20 @@ export function createCatalog(context, region) {
43
73
  handle,
44
74
  limit: 1,
45
75
  });
46
- return products[0] ?? null;
76
+ return (await visibleProducts(products))[0] ?? null;
47
77
  },
48
78
  /**
49
79
  * The top-level categories with all descendants (e.g. "Anwendungsbereiche"
50
80
  * and "Inhaltsstoffe" with their entries) - the filter trees of a product
51
- * list; filter with `filterProductsByCategories` from /core.
81
+ * list; filter with `filterProductsByCategories` from /core. Partner-only
82
+ * categories only for an active partner.
52
83
  */
53
84
  async listCategoryTrees() {
54
- const sdk = context.createMedusaClient();
55
- const { product_categories: productCategories } = await sdk.store.category.list({
56
- fields: "id,name,handle,description,metadata,rank,*category_children",
57
- include_descendants_tree: true,
58
- limit: catalogFetchLimit,
59
- parent_category_id: "null",
60
- });
61
- return productCategories;
85
+ const trees = await loadCategoryTrees();
86
+ if (collectPartnerOnlyCategoryIds(trees).size === 0) {
87
+ return trees;
88
+ }
89
+ return (await isActivePartner()) ? trees : withoutPartnerOnlyCategories(trees);
62
90
  },
63
91
  async categoryByPath(categoryPath) {
64
92
  const sdk = context.createMedusaClient();
@@ -67,7 +95,11 @@ export function createCatalog(context, region) {
67
95
  handle: categoryPath.join("/"),
68
96
  limit: 1,
69
97
  });
70
- 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;
71
103
  },
72
104
  async collectionByHandle(handle) {
73
105
  const sdk = context.createMedusaClient();