@hartl-services/medusa-storefront 0.3.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.
@@ -790,6 +790,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
790
790
  inventory_item_id: string;
791
791
  batch_id: string | null;
792
792
  expected_quantity: number;
793
+ product_title: string | null;
794
+ variant_title: string | null;
795
+ batch_name: string | null;
796
+ batch_expiry_date: string | null;
793
797
  }[];
794
798
  }, Error, {
795
799
  period_start: string;
@@ -797,6 +801,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
797
801
  inventory_item_id: string;
798
802
  batch_id: string | null;
799
803
  expected_quantity: number;
804
+ product_title: string | null;
805
+ variant_title: string | null;
806
+ batch_name: string | null;
807
+ batch_expiry_date: string | null;
800
808
  }[];
801
809
  }, readonly ["affiliate", "consignment", "inventory"]>, "queryFn"> & {
802
810
  queryFn?: import("@tanstack/react-query").QueryFunction<{
@@ -805,6 +813,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
805
813
  inventory_item_id: string;
806
814
  batch_id: string | null;
807
815
  expected_quantity: number;
816
+ product_title: string | null;
817
+ variant_title: string | null;
818
+ batch_name: string | null;
819
+ batch_expiry_date: string | null;
808
820
  }[];
809
821
  }, readonly ["affiliate", "consignment", "inventory"], never> | undefined;
810
822
  } & import("@tanstack/react-query").QueryKeyWithDataTag<readonly ["affiliate", "consignment", "inventory"], {
@@ -813,6 +825,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
813
825
  inventory_item_id: string;
814
826
  batch_id: string | null;
815
827
  expected_quantity: number;
828
+ product_title: string | null;
829
+ variant_title: string | null;
830
+ batch_name: string | null;
831
+ batch_expiry_date: string | null;
816
832
  }[];
817
833
  }, Error>;
818
834
  affiliateConsignmentReports: () => import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<{
@@ -838,6 +854,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
838
854
  resolved_unit_price: number | null;
839
855
  discounted_line_total: number | null;
840
856
  currency_code: string | null;
857
+ product_title: string | null;
858
+ variant_title: string | null;
859
+ batch_name: string | null;
860
+ batch_expiry_date: string | null;
841
861
  }[];
842
862
  }[];
843
863
  }, Error, {
@@ -863,6 +883,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
863
883
  resolved_unit_price: number | null;
864
884
  discounted_line_total: number | null;
865
885
  currency_code: string | null;
886
+ product_title: string | null;
887
+ variant_title: string | null;
888
+ batch_name: string | null;
889
+ batch_expiry_date: string | null;
866
890
  }[];
867
891
  }[];
868
892
  }, readonly ["affiliate", "consignment", "reports"]>, "queryFn"> & {
@@ -889,6 +913,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
889
913
  resolved_unit_price: number | null;
890
914
  discounted_line_total: number | null;
891
915
  currency_code: string | null;
916
+ product_title: string | null;
917
+ variant_title: string | null;
918
+ batch_name: string | null;
919
+ batch_expiry_date: string | null;
892
920
  }[];
893
921
  }[];
894
922
  }, readonly ["affiliate", "consignment", "reports"], never> | undefined;
@@ -915,6 +943,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
915
943
  resolved_unit_price: number | null;
916
944
  discounted_line_total: number | null;
917
945
  currency_code: string | null;
946
+ product_title: string | null;
947
+ variant_title: string | null;
948
+ batch_name: string | null;
949
+ batch_expiry_date: string | null;
918
950
  }[];
919
951
  }[];
920
952
  }, Error>;
@@ -648,6 +648,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
648
648
  inventory_item_id: string;
649
649
  batch_id: string | null;
650
650
  expected_quantity: number;
651
+ product_title: string | null;
652
+ variant_title: string | null;
653
+ batch_name: string | null;
654
+ batch_expiry_date: string | null;
651
655
  }[];
652
656
  }, Error, {
653
657
  period_start: string;
@@ -655,6 +659,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
655
659
  inventory_item_id: string;
656
660
  batch_id: string | null;
657
661
  expected_quantity: number;
662
+ product_title: string | null;
663
+ variant_title: string | null;
664
+ batch_name: string | null;
665
+ batch_expiry_date: string | null;
658
666
  }[];
659
667
  }, readonly ["affiliate", "consignment", "inventory"]>, "queryFn"> & {
660
668
  queryFn?: import("@tanstack/react-query").QueryFunction<{
@@ -663,6 +671,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
663
671
  inventory_item_id: string;
664
672
  batch_id: string | null;
665
673
  expected_quantity: number;
674
+ product_title: string | null;
675
+ variant_title: string | null;
676
+ batch_name: string | null;
677
+ batch_expiry_date: string | null;
666
678
  }[];
667
679
  }, readonly ["affiliate", "consignment", "inventory"], never> | undefined;
668
680
  } & import("@tanstack/react-query").QueryKeyWithDataTag<readonly ["affiliate", "consignment", "inventory"], {
@@ -671,6 +683,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
671
683
  inventory_item_id: string;
672
684
  batch_id: string | null;
673
685
  expected_quantity: number;
686
+ product_title: string | null;
687
+ variant_title: string | null;
688
+ batch_name: string | null;
689
+ batch_expiry_date: string | null;
674
690
  }[];
675
691
  }, Error>;
676
692
  affiliateConsignmentReports: () => import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<{
@@ -696,6 +712,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
696
712
  resolved_unit_price: number | null;
697
713
  discounted_line_total: number | null;
698
714
  currency_code: string | null;
715
+ product_title: string | null;
716
+ variant_title: string | null;
717
+ batch_name: string | null;
718
+ batch_expiry_date: string | null;
699
719
  }[];
700
720
  }[];
701
721
  }, Error, {
@@ -721,6 +741,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
721
741
  resolved_unit_price: number | null;
722
742
  discounted_line_total: number | null;
723
743
  currency_code: string | null;
744
+ product_title: string | null;
745
+ variant_title: string | null;
746
+ batch_name: string | null;
747
+ batch_expiry_date: string | null;
724
748
  }[];
725
749
  }[];
726
750
  }, readonly ["affiliate", "consignment", "reports"]>, "queryFn"> & {
@@ -747,6 +771,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
747
771
  resolved_unit_price: number | null;
748
772
  discounted_line_total: number | null;
749
773
  currency_code: string | null;
774
+ product_title: string | null;
775
+ variant_title: string | null;
776
+ batch_name: string | null;
777
+ batch_expiry_date: string | null;
750
778
  }[];
751
779
  }[];
752
780
  }, readonly ["affiliate", "consignment", "reports"], never> | undefined;
@@ -773,6 +801,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
773
801
  resolved_unit_price: number | null;
774
802
  discounted_line_total: number | null;
775
803
  currency_code: string | null;
804
+ product_title: string | null;
805
+ variant_title: string | null;
806
+ batch_name: string | null;
807
+ batch_expiry_date: string | null;
776
808
  }[];
777
809
  }[];
778
810
  }, Error>;
@@ -0,0 +1,19 @@
1
+ import type { ConsignmentLineLabels } from "@hartl-services/medusa-contracts/types/affiliate-store";
2
+ /**
3
+ * Display parts of a consignment line (stock line or report line). The ids
4
+ * stay the fallback while a product or batch cannot be resolved; the shop
5
+ * puts the parts into its own wording.
6
+ */
7
+ export interface ConsignmentLineDisplay {
8
+ /** "Product – Variant", the product alone, or null without a product. */
9
+ product: string | null;
10
+ /** Batch name, or the batch id while the name is unknown; null without batch. */
11
+ batch: string | null;
12
+ /** Expiry date as written for `locale`, or null. */
13
+ expiryDate: string | null;
14
+ }
15
+ export type ConsignmentLineDisplayInput = Partial<ConsignmentLineLabels> & {
16
+ inventory_item_id: string;
17
+ batch_id: string | null;
18
+ };
19
+ export declare function getConsignmentLineDisplay(line: ConsignmentLineDisplayInput, locale: string): ConsignmentLineDisplay;
@@ -0,0 +1,18 @@
1
+ export function getConsignmentLineDisplay(line, locale) {
2
+ const product = line.product_title?.trim() || null;
3
+ const variant = line.variant_title?.trim() || null;
4
+ const productLabel = product && variant && variant !== product ? `${product} – ${variant}` : (product ?? variant);
5
+ return {
6
+ product: productLabel,
7
+ batch: line.batch_id ? line.batch_name?.trim() || line.batch_id : null,
8
+ expiryDate: line.batch_id && line.batch_expiry_date ? formatCalendarDate(line.batch_expiry_date, locale) : null,
9
+ };
10
+ }
11
+ // A calendar date (YYYY-MM-DD) has no time zone; formatting it in UTC keeps
12
+ // the day the backend sent.
13
+ function formatCalendarDate(value, locale) {
14
+ const date = new Date(`${value}T00:00:00.000Z`);
15
+ return Number.isNaN(date.getTime())
16
+ ? value
17
+ : new Intl.DateTimeFormat(locale, { day: "2-digit", month: "2-digit", year: "numeric", timeZone: "UTC" }).format(date);
18
+ }
@@ -5,6 +5,7 @@ export * from "./affiliate/affiliate-coupon-code-presentation.js";
5
5
  export * from "./affiliate/affiliate-coupon-label.js";
6
6
  export * from "./affiliate/affiliate-date-range.js";
7
7
  export * from "./affiliate/affiliate-rate-presentation.js";
8
+ export * from "./affiliate/consignment-line-labels.js";
8
9
  export * from "./affiliate/partner-company-address.js";
9
10
  export * from "./auth/customer-sign-in.js";
10
11
  export * from "./auth/link-token.js";
@@ -6,6 +6,7 @@ export * from "./affiliate/affiliate-coupon-code-presentation.js";
6
6
  export * from "./affiliate/affiliate-coupon-label.js";
7
7
  export * from "./affiliate/affiliate-date-range.js";
8
8
  export * from "./affiliate/affiliate-rate-presentation.js";
9
+ export * from "./affiliate/consignment-line-labels.js";
9
10
  export * from "./affiliate/partner-company-address.js";
10
11
  export * from "./auth/customer-sign-in.js";
11
12
  export * from "./auth/link-token.js";
@@ -513,6 +513,10 @@ export declare function useAffiliateConsignment(): {
513
513
  inventory_item_id: string;
514
514
  batch_id: string | null;
515
515
  expected_quantity: number;
516
+ product_title: string | null;
517
+ variant_title: string | null;
518
+ batch_name: string | null;
519
+ batch_expiry_date: string | null;
516
520
  }[];
517
521
  }, Error>;
518
522
  isActive: boolean;
@@ -540,6 +544,10 @@ export declare function useAffiliateConsignment(): {
540
544
  resolved_unit_price: number | null;
541
545
  discounted_line_total: number | null;
542
546
  currency_code: string | null;
547
+ product_title: string | null;
548
+ variant_title: string | null;
549
+ batch_name: string | null;
550
+ batch_expiry_date: string | null;
543
551
  }[];
544
552
  }[];
545
553
  }, Error>;
@@ -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.3.0",
8
- "@hartl-services/medusa-contracts@^1.2.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.3.0",
8
- "@hartl-services/medusa-contracts@^1.2.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.3.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.3.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.3.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.3.0",
11
- "@hartl-services/medusa-contracts@^1.2.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.3.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.3.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.3.0",
310
- "@hartl-services/medusa-contracts@^1.2.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.3.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.3.0",
665
- "@hartl-services/medusa-contracts@^1.2.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.3.0",
8
- "@hartl-services/medusa-contracts@^1.2.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",
@@ -108,6 +108,10 @@ export declare function createAffiliate(context: ServerContext, pluginFeatures:
108
108
  resolved_unit_price: number | null;
109
109
  discounted_line_total: number | null;
110
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;
111
115
  }[];
112
116
  };
113
117
  draft_order: {
@@ -216,6 +216,10 @@ export declare function createStorefrontServer(config: StorefrontServerConfig):
216
216
  resolved_unit_price: number | null;
217
217
  discounted_line_total: number | null;
218
218
  currency_code: string | null;
219
+ product_title: string | null;
220
+ variant_title: string | null;
221
+ batch_name: string | null;
222
+ batch_expiry_date: string | null;
219
223
  }[];
220
224
  };
221
225
  draft_order: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hartl-services/medusa-storefront",
3
- "version": "0.3.0",
3
+ "version": "0.4.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.2.0",
48
+ "@hartl-services/medusa-contracts": "^1.3.0",
49
49
  "server-only": "^0.0.1"
50
50
  },
51
51
  "peerDependencies": {