@wilshop/dashboard 3.5.6 → 3.5.7

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 (133) hide show
  1. package/dist/plugin/dashboard.plugin.d.ts +1 -1
  2. package/dist/plugin/dashboard.plugin.js +1 -1
  3. package/dist/vite/utils/compiler.js +50 -24
  4. package/dist/vite/utils/path-transformer.d.ts +20 -0
  5. package/dist/vite/utils/path-transformer.js +116 -0
  6. package/dist/vite/utils/plugin-discovery.js +3 -2
  7. package/dist/vite/utils/ui-config.js +15 -1
  8. package/dist/vite/vite-plugin-lingui-babel.d.ts +15 -2
  9. package/dist/vite/vite-plugin-lingui-babel.js +90 -8
  10. package/dist/vite/vite-plugin-translations.js +2 -2
  11. package/dist/vite/vite-plugin-ui-config.d.ts +31 -0
  12. package/package.json +10 -6
  13. package/src/app/common/delete-bulk-action.tsx +1 -1
  14. package/src/app/common/duplicate-bulk-action.tsx +1 -1
  15. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -3
  16. package/src/app/routes/_authenticated/_collections/collections.tsx +169 -48
  17. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +36 -5
  18. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +1 -1
  19. package/src/app/routes/_authenticated/_collections/components/collection-filters-selector.tsx +7 -1
  20. package/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx +31 -29
  21. package/src/app/routes/_authenticated/_customers/customers.graphql.ts +1 -0
  22. package/src/app/routes/_authenticated/_customers/customers.tsx +3 -0
  23. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +1 -1
  24. package/src/app/routes/_authenticated/_orders/components/draft-order-status.tsx +48 -0
  25. package/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx +8 -5
  26. package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +79 -54
  27. package/src/app/routes/_authenticated/_orders/components/order-history/default-order-history-components.tsx +43 -3
  28. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +19 -3
  29. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +1 -0
  30. package/src/app/routes/_authenticated/_orders/components/refund-order-dialog.tsx +372 -0
  31. package/src/app/routes/_authenticated/_orders/hooks/use-refund-order.ts +345 -0
  32. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +41 -0
  33. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +22 -6
  34. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +51 -0
  35. package/src/app/routes/_authenticated/_orders/utils/refund-utils.ts +100 -0
  36. package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +1 -1
  37. package/src/app/routes/_authenticated/_payment-methods/components/payment-eligibility-checker-selector.tsx +4 -1
  38. package/src/app/routes/_authenticated/_payment-methods/components/payment-handler-selector.tsx +7 -1
  39. package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +18 -2
  40. package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +1 -1
  41. package/src/app/routes/_authenticated/_product-variants/components/variant-price-detail.tsx +6 -2
  42. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +9 -3
  43. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +49 -30
  44. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +1 -1
  45. package/src/app/routes/_authenticated/_profile/profile.graphql.ts +7 -0
  46. package/src/app/routes/_authenticated/_profile/profile.tsx +25 -1
  47. package/src/app/routes/_authenticated/_promotions/components/promotion-actions-selector.tsx +7 -1
  48. package/src/app/routes/_authenticated/_promotions/components/promotion-conditions-selector.tsx +7 -1
  49. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +18 -2
  50. package/src/app/routes/_authenticated/_shipping-methods/components/shipping-calculator-selector.tsx +7 -1
  51. package/src/app/routes/_authenticated/_shipping-methods/components/shipping-eligibility-checker-selector.tsx +4 -1
  52. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +14 -2
  53. package/src/i18n/common-strings.ts +7 -0
  54. package/src/i18n/locales/ar.po +669 -399
  55. package/src/i18n/locales/bg.po +1889 -46
  56. package/src/i18n/locales/cs.po +676 -406
  57. package/src/i18n/locales/de.po +676 -406
  58. package/src/i18n/locales/en.po +669 -399
  59. package/src/i18n/locales/es.po +676 -406
  60. package/src/i18n/locales/fa.po +676 -406
  61. package/src/i18n/locales/fr.po +676 -406
  62. package/src/i18n/locales/he.po +676 -406
  63. package/src/i18n/locales/hr.po +676 -406
  64. package/src/i18n/locales/it.po +676 -406
  65. package/src/i18n/locales/ja.po +676 -406
  66. package/src/i18n/locales/nb.po +676 -406
  67. package/src/i18n/locales/ne.po +676 -406
  68. package/src/i18n/locales/pl.po +676 -406
  69. package/src/i18n/locales/pt_BR.po +676 -406
  70. package/src/i18n/locales/pt_PT.po +676 -406
  71. package/src/i18n/locales/ru.po +676 -406
  72. package/src/i18n/locales/sv.po +676 -406
  73. package/src/i18n/locales/tr.po +676 -406
  74. package/src/i18n/locales/uk.po +676 -406
  75. package/src/i18n/locales/zh_Hans.po +676 -406
  76. package/src/i18n/locales/zh_Hant.po +676 -406
  77. package/src/lib/components/data-input/facet-value-input.tsx +2 -2
  78. package/src/lib/components/data-input/index.ts +1 -0
  79. package/src/lib/components/data-input/select-with-options.tsx +23 -7
  80. package/src/lib/components/data-input/struct-form-input.tsx +53 -21
  81. package/src/lib/components/data-input/text-input.tsx +1 -1
  82. package/src/lib/components/data-table/data-table-bulk-actions.tsx +2 -1
  83. package/src/lib/components/data-table/data-table-context.tsx +2 -10
  84. package/src/lib/components/data-table/data-table-utils.ts +34 -12
  85. package/src/lib/components/data-table/data-table.tsx +68 -30
  86. package/src/lib/components/data-table/global-views-bar.tsx +1 -1
  87. package/src/lib/components/data-table/my-views-button.tsx +1 -1
  88. package/src/lib/components/data-table/save-view-button.tsx +1 -1
  89. package/src/lib/components/data-table/use-generated-columns.tsx +9 -2
  90. package/src/lib/components/data-table/views-sheet.tsx +1 -1
  91. package/src/lib/components/layout/channel-switcher.tsx +16 -17
  92. package/src/lib/components/layout/manage-languages-dialog.tsx +1 -1
  93. package/src/lib/components/shared/assign-to-channel-bulk-action.tsx +1 -1
  94. package/src/lib/components/shared/configurable-operation-input.tsx +23 -0
  95. package/src/lib/components/shared/configurable-operation-multi-selector.tsx +45 -0
  96. package/src/lib/components/shared/configurable-operation-selector.tsx +5 -0
  97. package/src/lib/components/shared/paginated-list-context.ts +10 -0
  98. package/src/lib/components/shared/paginated-list-data-table.tsx +6 -32
  99. package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +1 -1
  100. package/src/lib/components/ui/alert.tsx +2 -0
  101. package/src/lib/constants.ts +7 -319
  102. package/src/lib/framework/dashboard-widget/base-widget.tsx +3 -12
  103. package/src/lib/framework/dashboard-widget/latest-orders-widget/index.tsx +1 -1
  104. package/src/lib/framework/dashboard-widget/metrics-widget/chart.tsx +1 -1
  105. package/src/lib/framework/dashboard-widget/metrics-widget/index.tsx +1 -1
  106. package/src/lib/framework/dashboard-widget/orders-summary/index.tsx +1 -1
  107. package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +2 -20
  108. package/src/lib/framework/extension-api/input-component-extensions.tsx +4 -0
  109. package/src/lib/framework/form-engine/custom-form-component.tsx +13 -3
  110. package/src/lib/framework/form-engine/form-engine-types.ts +3 -5
  111. package/src/lib/framework/form-engine/form-schema-tools.ts +4 -1
  112. package/src/lib/framework/form-engine/use-generated-form.tsx +6 -2
  113. package/src/lib/framework/form-engine/utils.spec.ts +129 -2
  114. package/src/lib/framework/form-engine/utils.ts +36 -9
  115. package/src/lib/framework/form-engine/value-transformers.ts +6 -0
  116. package/src/lib/framework/page/detail-page-route-loader.tsx +6 -4
  117. package/src/lib/framework/page/detail-page.tsx +22 -37
  118. package/src/lib/framework/page/list-page.stories.tsx +41 -2
  119. package/src/lib/framework/page/list-page.tsx +8 -0
  120. package/src/lib/graphql/graphql-env.d.ts +33 -16
  121. package/src/lib/graphql/schema-enums.ts +13 -0
  122. package/src/lib/hooks/use-alerts-context.ts +10 -0
  123. package/src/lib/hooks/use-alerts.ts +1 -1
  124. package/src/lib/hooks/use-data-table-context.ts +11 -0
  125. package/src/lib/hooks/use-dynamic-translations.ts +7 -0
  126. package/src/lib/hooks/use-job-queue-polling.ts +160 -0
  127. package/src/lib/hooks/use-paginated-list.ts +28 -0
  128. package/src/lib/hooks/use-widget-dimensions.ts +12 -0
  129. package/src/lib/hooks/use-widget-filters.ts +21 -0
  130. package/src/lib/index.ts +12 -0
  131. package/src/lib/providers/alerts-provider.tsx +3 -11
  132. package/src/lib/virtual.d.ts +5 -0
  133. package/src/lib/utils/global-languages.ts +0 -268
@@ -21,6 +21,7 @@ import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-lo
21
21
  import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
22
22
  import { Trans, useLingui } from '@lingui/react/macro';
23
23
  import { createFileRoute, useNavigate } from '@tanstack/react-router';
24
+ import { useState } from 'react';
24
25
  import { toast } from 'sonner';
25
26
  import { PaymentEligibilityCheckerSelector } from './components/payment-eligibility-checker-selector.js';
26
27
  import { PaymentHandlerSelector } from './components/payment-handler-selector.js';
@@ -82,6 +83,7 @@ function PaymentMethodDetailPage() {
82
83
  languageCode: translation.languageCode,
83
84
  name: translation.name,
84
85
  description: translation.description,
86
+ customFields: (translation as any).customFields,
85
87
  })),
86
88
  customFields: entity.customFields,
87
89
  };
@@ -115,6 +117,9 @@ function PaymentMethodDetailPage() {
115
117
  },
116
118
  });
117
119
 
120
+ const [checkerArgsValid, setCheckerArgsValid] = useState(true);
121
+ const [handlerArgsValid, setHandlerArgsValid] = useState(true);
122
+
118
123
  return (
119
124
  <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
120
125
  <PageTitle>
@@ -125,7 +130,13 @@ function PaymentMethodDetailPage() {
125
130
  <PermissionGuard requires={['UpdatePaymentMethod']}>
126
131
  <Button
127
132
  type="submit"
128
- disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
133
+ disabled={
134
+ !form.formState.isDirty ||
135
+ !form.formState.isValid ||
136
+ isPending ||
137
+ !checkerArgsValid ||
138
+ !handlerArgsValid
139
+ }
129
140
  >
130
141
  {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
131
142
  </Button>
@@ -178,6 +189,7 @@ function PaymentMethodDetailPage() {
178
189
  <PaymentEligibilityCheckerSelector
179
190
  value={field.value}
180
191
  onChange={field.onChange}
192
+ onValidityChange={setCheckerArgsValid}
181
193
  />
182
194
  )}
183
195
  />
@@ -187,7 +199,11 @@ function PaymentMethodDetailPage() {
187
199
  control={form.control}
188
200
  name="handler"
189
201
  render={({ field }) => (
190
- <PaymentHandlerSelector value={field.value} onChange={field.onChange} />
202
+ <PaymentHandlerSelector
203
+ value={field.value}
204
+ onChange={field.onChange}
205
+ onValidityChange={setHandlerArgsValid}
206
+ />
191
207
  )}
192
208
  />
193
209
  </PageBlock>
@@ -4,7 +4,7 @@ import { useState } from 'react';
4
4
  import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
5
5
  import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-channel-bulk-action.js';
6
6
  import { usePriceFactor } from '@/vdb/components/shared/assign-to-channel-dialog.js';
7
- import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
7
+ import { usePaginatedList } from '@/vdb/hooks/use-paginated-list.js';
8
8
  import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
9
9
  import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
10
10
  import { api } from '@/vdb/graphql/api.js';
@@ -70,8 +70,12 @@ export function VariantPriceDetail({
70
70
  }, [taxRatesData, activeChannel, taxCategoryId]);
71
71
 
72
72
  useEffect(() => {
73
- setGrossPrice(Math.round((price ?? 0) * ((100 + taxRate) / 100)));
74
- }, [price, taxRate]);
73
+ if (priceIncludesTax) {
74
+ setGrossPrice(price ?? 0);
75
+ } else {
76
+ setGrossPrice(Math.round((price ?? 0) * ((100 + taxRate) / 100)));
77
+ }
78
+ }, [price, taxRate, priceIncludesTax]);
75
79
 
76
80
  return (
77
81
  <div className="space-y-1">
@@ -32,6 +32,13 @@ export const productVariantListDocument = graphql(
32
32
  [assetFragment],
33
33
  );
34
34
 
35
+ export const productVariantPriceFragment = graphql(`
36
+ fragment ProductVariantPrice on ProductVariantPrice {
37
+ currencyCode
38
+ price
39
+ }
40
+ `);
41
+
35
42
  export const productVariantDetailDocument = graphql(
36
43
  `
37
44
  query ProductVariantDetail($id: ID!) {
@@ -86,8 +93,7 @@ export const productVariantDetailDocument = graphql(
86
93
  price
87
94
  priceWithTax
88
95
  prices {
89
- currencyCode
90
- price
96
+ ...ProductVariantPrice
91
97
  }
92
98
  trackInventory
93
99
  outOfStockThreshold
@@ -105,7 +111,7 @@ export const productVariantDetailDocument = graphql(
105
111
  }
106
112
  }
107
113
  `,
108
- [assetFragment],
114
+ [assetFragment, productVariantPriceFragment],
109
115
  );
110
116
 
111
117
  export const createProductVariantDocument = graphql(`
@@ -1,6 +1,7 @@
1
1
  import { MoneyInput } from '@/vdb/components/data-input/money-input.js';
2
2
  import { NumberInput } from '@/vdb/components/data-input/number-input.js';
3
3
  import { AssignedFacetValues } from '@/vdb/components/shared/assigned-facet-values.js';
4
+ import { CustomFieldsForm } from '@/vdb/components/shared/custom-fields-form.js';
4
5
  import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
5
6
  import { EntityAssets } from '@/vdb/components/shared/entity-assets.js';
6
7
  import { ErrorPage } from '@/vdb/components/shared/error-page.js';
@@ -12,8 +13,10 @@ import { Button } from '@/vdb/components/ui/button.js';
12
13
  import { FormControl, FormDescription, FormItem, FormLabel, FormMessage } from '@/vdb/components/ui/form.js';
13
14
  import { Input } from '@/vdb/components/ui/input.js';
14
15
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/vdb/components/ui/select.js';
16
+ import { Separator } from '@/vdb/components/ui/separator.js';
15
17
  import { Switch } from '@/vdb/components/ui/switch.js';
16
18
  import { NEW_ENTITY_PATH } from '@/vdb/constants.js';
19
+ import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
17
20
  import {
18
21
  CustomFieldsPageBlock,
19
22
  DetailFormGrid,
@@ -34,6 +37,7 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router';
34
37
  import { VariablesOf } from 'gql.tada';
35
38
  import { Trash } from 'lucide-react';
36
39
  import { toast } from 'sonner';
40
+
37
41
  import { AddCurrencyDropdown } from './components/add-currency-dropdown.js';
38
42
  import { AddStockLocationDropdown } from './components/add-stock-location-dropdown.js';
39
43
  import { VariantPriceDetail } from './components/variant-price-detail.js';
@@ -50,7 +54,10 @@ export const Route = createFileRoute('/_authenticated/_product-variants/product-
50
54
  component: ProductVariantDetailPage,
51
55
  loader: detailPageRouteLoader({
52
56
  pageId,
53
- queryDocument: productVariantDetailDocument,
57
+ queryDocument: () =>
58
+ addCustomFields(productVariantDetailDocument, {
59
+ includeNestedFragments: ['ProductVariantPrice'],
60
+ }),
54
61
  breadcrumb(_isNew, entity, location) {
55
62
  if ((location.search as any).from === 'product') {
56
63
  return [
@@ -81,7 +88,9 @@ function ProductVariantDetailPage() {
81
88
 
82
89
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
83
90
  pageId,
84
- queryDocument: productVariantDetailDocument,
91
+ queryDocument: addCustomFields(productVariantDetailDocument, {
92
+ includeNestedFragments: ['ProductVariantPrice'],
93
+ }),
85
94
  createDocument: createProductVariantDocument,
86
95
  updateDocument: updateProductVariantDocument,
87
96
  setValuesForUpdate: entity => {
@@ -169,6 +178,7 @@ function ProductVariantDetailPage() {
169
178
  currencyCode,
170
179
  price: 0,
171
180
  delete: false,
181
+ customFields: {},
172
182
  } as PriceInput;
173
183
  form.setValue('prices', [...currentPrices, newPrice], {
174
184
  shouldDirty: true,
@@ -274,37 +284,46 @@ function ProductVariantDetailPage() {
274
284
  </div>
275
285
  );
276
286
  return (
277
- <DetailFormGrid key={price.currencyCode}>
278
- <div className="flex gap-1 items-end">
279
- <FormFieldWrapper
280
- control={form.control}
281
- name={`prices.${actualIndex}.price`}
282
- label={priceLabel}
283
- render={({ field }) => (
284
- <MoneyInput {...field} currency={price.currencyCode} />
287
+ <div key={price.currencyCode} className="space-y-6">
288
+ {displayIndex > 0 && <Separator className="my-4" />}
289
+ <DetailFormGrid key={price.currencyCode}>
290
+ <div className="flex gap-1 items-end">
291
+ <FormFieldWrapper
292
+ control={form.control}
293
+ name={`prices.${actualIndex}.price`}
294
+ label={priceLabel}
295
+ render={({ field }) => (
296
+ <MoneyInput {...field} currency={price.currencyCode} />
297
+ )}
298
+ />
299
+ {activePrices.length > 1 && (
300
+ <Button
301
+ type="button"
302
+ variant="ghost"
303
+ size="sm"
304
+ onClick={() => handleRemoveCurrency(actualIndex)}
305
+ className="h-6 w-6 p-0 mb-2 hover:text-destructive hover:bg-destructive-100"
306
+ >
307
+ <Trash className="size-4" />
308
+ </Button>
285
309
  )}
310
+ </div>
311
+ <VariantPriceDetail
312
+ priceIncludesTax={activeChannel?.pricesIncludeTax ?? false}
313
+ price={price.price}
314
+ currencyCode={
315
+ price.currencyCode ?? activeChannel?.defaultCurrencyCode ?? ''
316
+ }
317
+ taxCategoryId={taxCategoryId}
286
318
  />
287
- {activePrices.length > 1 && (
288
- <Button
289
- type="button"
290
- variant="ghost"
291
- size="sm"
292
- onClick={() => handleRemoveCurrency(actualIndex)}
293
- className="h-6 w-6 p-0 mb-2 hover:text-destructive hover:bg-destructive-100"
294
- >
295
- <Trash className="size-4" />
296
- </Button>
297
- )}
298
- </div>
299
- <VariantPriceDetail
300
- priceIncludesTax={activeChannel?.pricesIncludeTax ?? false}
301
- price={price.price}
302
- currencyCode={
303
- price.currencyCode ?? activeChannel?.defaultCurrencyCode ?? ''
304
- }
305
- taxCategoryId={taxCategoryId}
319
+ </DetailFormGrid>
320
+ {/* Custom fields for ProductVariantPrice */}
321
+ <CustomFieldsForm
322
+ entityType="ProductVariantPrice"
323
+ control={form.control}
324
+ formPathPrefix={`prices.${actualIndex}`}
306
325
  />
307
- </DetailFormGrid>
326
+ </div>
308
327
  );
309
328
  })}
310
329
  {unusedCurrencies.length ? (
@@ -4,7 +4,7 @@ import { useState } from 'react';
4
4
  import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
5
5
  import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-channel-bulk-action.js';
6
6
  import { usePriceFactor } from '@/vdb/components/shared/assign-to-channel-dialog.js';
7
- import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
7
+ import { usePaginatedList } from '@/vdb/hooks/use-paginated-list.js';
8
8
  import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
9
9
  import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
10
10
  import { api } from '@/vdb/graphql/api.js';
@@ -10,6 +10,13 @@ export const activeAdministratorDocument = graphql(`
10
10
  lastName
11
11
  emailAddress
12
12
  customFields
13
+ user {
14
+ authenticationMethods {
15
+ id
16
+ strategy
17
+ createdAt
18
+ }
19
+ }
13
20
  }
14
21
  }
15
22
  `);
@@ -1,5 +1,6 @@
1
1
  import { ErrorPage } from '@/vdb/components/shared/error-page.js';
2
2
  import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
3
+ import { Badge } from '@/vdb/components/ui/badge.js';
3
4
  import { Button } from '@/vdb/components/ui/button.js';
4
5
  import { Input } from '@/vdb/components/ui/input.js';
5
6
  import {
@@ -14,6 +15,7 @@ import {
14
15
  } from '@/vdb/framework/layout-engine/page-layout.js';
15
16
  import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
16
17
  import { api } from '@/vdb/graphql/api.js';
18
+ import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
17
19
  import { Trans, useLingui } from '@lingui/react/macro';
18
20
  import { createFileRoute } from '@tanstack/react-router';
19
21
  import { toast } from 'sonner';
@@ -35,8 +37,9 @@ export const Route = createFileRoute('/_authenticated/_profile/profile')({
35
37
 
36
38
  function ProfilePage() {
37
39
  const { t } = useLingui();
40
+ const { formatDate } = useLocalFormat();
38
41
 
39
- const { form, submitHandler, isPending } = useDetailPage({
42
+ const { form, submitHandler, isPending, entity } = useDetailPage({
40
43
  queryDocument: activeAdministratorDocument,
41
44
  entityField: 'activeAdministrator',
42
45
  updateDocument: updateAdministratorDocument,
@@ -112,6 +115,27 @@ function ProfilePage() {
112
115
  />
113
116
  </DetailFormGrid>
114
117
  </PageBlock>
118
+ <PageBlock
119
+ column="side"
120
+ blockId="auth-methods"
121
+ title={<Trans>Authentication methods</Trans>}
122
+ >
123
+ <div className="space-y-2">
124
+ {entity?.user?.authenticationMethods.map(method => (
125
+ <div
126
+ key={method.id}
127
+ className="flex items-center justify-between py-2 border-b last:border-b-0"
128
+ >
129
+ <Badge variant="secondary">
130
+ {method.strategy === 'native' ? t`Password` : method.strategy}
131
+ </Badge>
132
+ <span className="text-sm text-muted-foreground">
133
+ <Trans>Added</Trans> {formatDate(method.createdAt)}
134
+ </span>
135
+ </div>
136
+ ))}
137
+ </div>
138
+ </PageBlock>
115
139
  <CustomFieldsPageBlock column="main" entityType="Administrator" control={form.control} />
116
140
  </PageLayout>
117
141
  </Page>
@@ -17,9 +17,14 @@ export const promotionActionsDocument = graphql(
17
17
  interface PromotionActionsSelectorProps {
18
18
  value: ConfigurableOperationInputType[];
19
19
  onChange: (value: ConfigurableOperationInputType[]) => void;
20
+ onValidityChange?: (isValid: boolean) => void;
20
21
  }
21
22
 
22
- export function PromotionActionsSelector({ value, onChange }: Readonly<PromotionActionsSelectorProps>) {
23
+ export function PromotionActionsSelector({
24
+ value,
25
+ onChange,
26
+ onValidityChange,
27
+ }: Readonly<PromotionActionsSelectorProps>) {
23
28
  return (
24
29
  <ConfigurableOperationMultiSelector
25
30
  value={value}
@@ -30,6 +35,7 @@ export function PromotionActionsSelector({ value, onChange }: Readonly<Promotion
30
35
  buttonText="Add action"
31
36
  dropdownTitle="Available Actions"
32
37
  showEnhancedDropdown={true}
38
+ onValidityChange={onValidityChange}
33
39
  />
34
40
  );
35
41
  }
@@ -17,9 +17,14 @@ export const promotionConditionsDocument = graphql(
17
17
  interface PromotionConditionsSelectorProps {
18
18
  value: ConfigurableOperationInputType[];
19
19
  onChange: (value: ConfigurableOperationInputType[]) => void;
20
+ onValidityChange?: (isValid: boolean) => void;
20
21
  }
21
22
 
22
- export function PromotionConditionsSelector({ value, onChange }: Readonly<PromotionConditionsSelectorProps>) {
23
+ export function PromotionConditionsSelector({
24
+ value,
25
+ onChange,
26
+ onValidityChange,
27
+ }: Readonly<PromotionConditionsSelectorProps>) {
23
28
  return (
24
29
  <ConfigurableOperationMultiSelector
25
30
  value={value}
@@ -30,6 +35,7 @@ export function PromotionConditionsSelector({ value, onChange }: Readonly<Promot
30
35
  buttonText="Add condition"
31
36
  dropdownTitle="Available Conditions"
32
37
  showEnhancedDropdown={true}
38
+ onValidityChange={onValidityChange}
33
39
  />
34
40
  );
35
41
  }
@@ -23,6 +23,7 @@ import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-lo
23
23
  import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
24
24
  import { Trans, useLingui } from '@lingui/react/macro';
25
25
  import { createFileRoute, useNavigate } from '@tanstack/react-router';
26
+ import { useState } from 'react';
26
27
  import { toast } from 'sonner';
27
28
  import { PromotionActionsSelector } from './components/promotion-actions-selector.js';
28
29
  import { PromotionConditionsSelector } from './components/promotion-conditions-selector.js';
@@ -93,6 +94,7 @@ function PromotionDetailPage() {
93
94
  languageCode: translation.languageCode,
94
95
  name: translation.name,
95
96
  description: translation.description,
97
+ customFields: (translation as any).customFields,
96
98
  })),
97
99
  customFields: entity.customFields,
98
100
  };
@@ -123,6 +125,9 @@ function PromotionDetailPage() {
123
125
  },
124
126
  });
125
127
 
128
+ const [conditionsArgsValid, setConditionsArgsValid] = useState(true);
129
+ const [actionsArgsValid, setActionsArgsValid] = useState(true);
130
+
126
131
  return (
127
132
  <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
128
133
  <PageTitle>{creatingNewEntity ? <Trans>New promotion</Trans> : (entity?.name ?? '')}</PageTitle>
@@ -131,7 +136,13 @@ function PromotionDetailPage() {
131
136
  <PermissionGuard requires={['UpdatePromotion']}>
132
137
  <Button
133
138
  type="submit"
134
- disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
139
+ disabled={
140
+ !form.formState.isDirty ||
141
+ !form.formState.isValid ||
142
+ isPending ||
143
+ !conditionsArgsValid ||
144
+ !actionsArgsValid
145
+ }
135
146
  >
136
147
  {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
137
148
  </Button>
@@ -226,6 +237,7 @@ function PromotionDetailPage() {
226
237
  <PromotionConditionsSelector
227
238
  value={field.value ?? []}
228
239
  onChange={field.onChange}
240
+ onValidityChange={setConditionsArgsValid}
229
241
  />
230
242
  )}
231
243
  />
@@ -235,7 +247,11 @@ function PromotionDetailPage() {
235
247
  control={form.control}
236
248
  name="actions"
237
249
  render={({ field }) => (
238
- <PromotionActionsSelector value={field.value ?? []} onChange={field.onChange} />
250
+ <PromotionActionsSelector
251
+ value={field.value ?? []}
252
+ onChange={field.onChange}
253
+ onValidityChange={setActionsArgsValid}
254
+ />
239
255
  )}
240
256
  />
241
257
  </PageBlock>
@@ -17,9 +17,14 @@ export const shippingCalculatorsDocument = graphql(
17
17
  interface ShippingCalculatorSelectorProps {
18
18
  value: ConfigurableOperationInputType | undefined;
19
19
  onChange: (value: ConfigurableOperationInputType | undefined) => void;
20
+ onValidityChange?: (isValid: boolean) => void;
20
21
  }
21
22
 
22
- export function ShippingCalculatorSelector({ value, onChange }: Readonly<ShippingCalculatorSelectorProps>) {
23
+ export function ShippingCalculatorSelector({
24
+ value,
25
+ onChange,
26
+ onValidityChange,
27
+ }: Readonly<ShippingCalculatorSelectorProps>) {
23
28
  return (
24
29
  <ConfigurableOperationSelector
25
30
  value={value}
@@ -28,6 +33,7 @@ export function ShippingCalculatorSelector({ value, onChange }: Readonly<Shippin
28
33
  queryKey="shippingCalculators"
29
34
  dataPath="shippingCalculators"
30
35
  buttonText="Select Shipping Calculator"
36
+ onValidityChange={onValidityChange}
31
37
  />
32
38
  );
33
39
  }
@@ -17,12 +17,14 @@ export const shippingEligibilityCheckersDocument = graphql(
17
17
  interface ShippingEligibilityCheckerSelectorProps {
18
18
  value: ConfigurableOperationInputType | undefined;
19
19
  onChange: (value: ConfigurableOperationInputType | undefined) => void;
20
+ onValidityChange?: (isValid: boolean) => void;
20
21
  }
21
22
 
22
23
  export function ShippingEligibilityCheckerSelector({
23
24
  value,
24
25
  onChange,
25
- }: ShippingEligibilityCheckerSelectorProps) {
26
+ onValidityChange,
27
+ }: Readonly<ShippingEligibilityCheckerSelectorProps>) {
26
28
  return (
27
29
  <ConfigurableOperationSelector
28
30
  value={value}
@@ -31,6 +33,7 @@ export function ShippingEligibilityCheckerSelector({
31
33
  queryKey="shippingEligibilityCheckers"
32
34
  dataPath="shippingEligibilityCheckers"
33
35
  buttonText="Select Shipping Eligibility Checker"
36
+ onValidityChange={onValidityChange}
34
37
  />
35
38
  );
36
39
  }
@@ -20,6 +20,7 @@ import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-lo
20
20
  import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
21
21
  import { Trans, useLingui } from '@lingui/react/macro';
22
22
  import { createFileRoute, useNavigate } from '@tanstack/react-router';
23
+ import { useState } from 'react';
23
24
  import { toast } from 'sonner';
24
25
  import { FulfillmentHandlerSelector } from './components/fulfillment-handler-selector.js';
25
26
  import { ShippingCalculatorSelector } from './components/shipping-calculator-selector.js';
@@ -79,6 +80,7 @@ function ShippingMethodDetailPage() {
79
80
  languageCode: translation.languageCode,
80
81
  name: translation.name,
81
82
  description: translation.description,
83
+ customFields: (translation as any).customFields,
82
84
  })),
83
85
  customFields: entity.customFields,
84
86
  };
@@ -108,6 +110,9 @@ function ShippingMethodDetailPage() {
108
110
  const checker = form.watch('checker');
109
111
  const calculator = form.watch('calculator');
110
112
 
113
+ const [checkerArgsValid, setCheckerArgsValid] = useState(true);
114
+ const [calculatorArgsValid, setCalculatorArgsValid] = useState(true);
115
+
111
116
  return (
112
117
  <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
113
118
  <PageTitle>
@@ -126,7 +131,9 @@ function ShippingMethodDetailPage() {
126
131
  !form.formState.isValid ||
127
132
  isPending ||
128
133
  !checker?.code ||
129
- !calculator?.code
134
+ !calculator?.code ||
135
+ !checkerArgsValid ||
136
+ !calculatorArgsValid
130
137
  }
131
138
  >
132
139
  {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
@@ -178,6 +185,7 @@ function ShippingMethodDetailPage() {
178
185
  <ShippingEligibilityCheckerSelector
179
186
  value={field.value}
180
187
  onChange={field.onChange}
188
+ onValidityChange={setCheckerArgsValid}
181
189
  />
182
190
  )}
183
191
  />
@@ -187,7 +195,11 @@ function ShippingMethodDetailPage() {
187
195
  control={form.control}
188
196
  name="calculator"
189
197
  render={({ field }) => (
190
- <ShippingCalculatorSelector value={field.value} onChange={field.onChange} />
198
+ <ShippingCalculatorSelector
199
+ value={field.value}
200
+ onChange={field.onChange}
201
+ onValidityChange={setCalculatorArgsValid}
202
+ />
191
203
  )}
192
204
  />
193
205
  </PageBlock>
@@ -39,6 +39,13 @@ const commonI18nString = {
39
39
  /* i18n*/ 'orderState.Modifying',
40
40
  /* i18n*/ 'orderState.ArrangingAdditionalPayment',
41
41
  ],
42
+ refundReason: [
43
+ /* i18n*/ 'refundReason.CustomerRequest',
44
+ /* i18n*/ 'refundReason.NotAvailable',
45
+ /* i18n*/ 'refundReason.DamagedInShipping',
46
+ /* i18n*/ 'refundReason.WrongItem',
47
+ /* i18n*/ 'refundReason.Other',
48
+ ],
42
49
  fieldName: [
43
50
  /* i18n*/ 'fieldName.attempts',
44
51
  /* i18n*/ 'fieldName.availableCurrencyCodes',