From c5c700681775f72f16f6b4c48c268c8e3be97579 Mon Sep 17 00:00:00 2001 From: Marco Montalbano Date: Tue, 15 Jul 2025 23:00:56 +0200 Subject: [PATCH 1/4] deps: move from eslint to biome --- .lintstagedrc.json | 3 + .ncurc.js | 19 +- .vscode/extensions.json | 6 +- .vscode/settings.json | 47 +- biome.jsonc | 52 + lerna.json | 2 +- package.json | 5 +- packages/app-elements/.eslintignore | 2 - packages/app-elements/.eslintrc.cjs | 15 - packages/app-elements/.lintstagedrc.json | 4 +- packages/app-elements/package.json | 5 +- packages/app-elements/postcss.config.cjs | 4 +- .../react-testing-library.config.js | 2 +- .../src/dictionaries/customers.ts | 46 +- .../app-elements/src/dictionaries/orders.ts | 248 +- .../src/dictionaries/promotions.ts | 60 +- .../app-elements/src/dictionaries/returns.ts | 98 +- .../src/dictionaries/shipments.ts | 132 +- .../src/dictionaries/stockTransfers.ts | 92 +- .../app-elements/src/dictionaries/types.ts | 6 +- .../src/helpers/appsNavigation.test.ts | 230 +- .../src/helpers/appsNavigation.ts | 50 +- .../app-elements/src/helpers/attachments.ts | 20 +- .../src/helpers/currencies.test.ts | 26 +- .../app-elements/src/helpers/currencies.ts | 3582 +- .../app-elements/src/helpers/date.test.ts | 792 +- packages/app-elements/src/helpers/date.ts | 224 +- .../src/helpers/downloadJsonAsFile.ts | 13 +- .../src/helpers/giftCards.test.ts | 24 +- .../app-elements/src/helpers/giftCards.ts | 4 +- .../app-elements/src/helpers/mocks.test.ts | 40 +- packages/app-elements/src/helpers/mocks.ts | 4 +- .../app-elements/src/helpers/name.test.ts | 40 +- packages/app-elements/src/helpers/name.ts | 12 +- .../src/helpers/resources.test.ts | 108 +- .../app-elements/src/helpers/resources.ts | 550 +- packages/app-elements/src/helpers/tracking.ts | 64 +- .../src/helpers/transactions.test.ts | 70 +- .../app-elements/src/helpers/transactions.ts | 8 +- .../src/helpers/unitsOfWeight.test.ts | 34 +- .../app-elements/src/helpers/unitsOfWeight.ts | 12 +- .../app-elements/src/helpers/useAppLinking.ts | 68 +- .../app-elements/src/hooks/useClickAway.ts | 59 +- .../src/hooks/useDelayShow.test.tsx | 22 +- .../app-elements/src/hooks/useDelayShow.ts | 2 +- .../src/hooks/useEditMetadataOverlay.tsx | 32 +- .../src/hooks/useEditTagsOverlay.tsx | 130 +- .../src/hooks/useIsChanged.test.tsx | 36 +- .../app-elements/src/hooks/useIsChanged.ts | 6 +- .../src/hooks/useOnBlurFromContainer.ts | 4 +- .../src/hooks/useOverlay.test.tsx | 43 +- .../app-elements/src/hooks/useOverlay.tsx | 16 +- packages/app-elements/src/locales/en.ts | 1034 +- packages/app-elements/src/locales/i18n.d.ts | 6 +- packages/app-elements/src/locales/it.ts | 1000 +- packages/app-elements/src/main.test.ts | 43 +- packages/app-elements/src/main.ts | 447 +- .../src/mocks/data/core_resources.ts | 66041 ++++++++-------- .../app-elements/src/mocks/data/countries.ts | 140 +- .../app-elements/src/mocks/data/customers.ts | 60 +- .../app-elements/src/mocks/data/markets.ts | 398 +- packages/app-elements/src/mocks/handlers.ts | 102 +- packages/app-elements/src/mocks/server.ts | 4 +- packages/app-elements/src/mocks/setup.ts | 14 +- packages/app-elements/src/mocks/stubs.ts | 22 +- .../CoreSdkProvider/CoreSdkProvider.tsx | 40 +- .../src/providers/CoreSdkProvider/index.tsx | 4 +- .../CoreSdkProvider/makeSdkClient.test.ts | 20 +- .../CoreSdkProvider/makeSdkClient.ts | 14 +- .../providers/CoreSdkProvider/useCoreApi.tsx | 28 +- .../src/providers/ErrorBoundary.tsx | 29 +- .../src/providers/GTMProvider.test.tsx | 44 +- .../src/providers/GTMProvider.tsx | 18 +- .../src/providers/I18NProvider.tsx | 36 +- .../src/providers/TokenProvider/MetaTags.tsx | 20 +- .../TokenProvider/MockTokenProvider.tsx | 46 +- .../TokenProvider/TokenProvider.test.tsx | 229 +- .../providers/TokenProvider/TokenProvider.tsx | 112 +- .../providers/TokenProvider/extras.test.ts | 106 +- .../src/providers/TokenProvider/extras.ts | 48 +- .../getAccessTokenFromUrl.test.ts | 52 +- .../TokenProvider/getAccessTokenFromUrl.ts | 32 +- .../TokenProvider/getInfoFromJwt.test.ts | 68 +- .../providers/TokenProvider/getInfoFromJwt.ts | 32 +- .../src/providers/TokenProvider/index.tsx | 10 +- .../src/providers/TokenProvider/reducer.ts | 40 +- .../providers/TokenProvider/storage.test.ts | 70 +- .../src/providers/TokenProvider/storage.ts | 26 +- .../src/providers/TokenProvider/types.ts | 67 +- .../src/providers/TokenProvider/url.test.ts | 16 +- .../src/providers/TokenProvider/url.ts | 14 +- .../TokenProvider/validateToken.test.ts | 62 +- .../providers/TokenProvider/validateToken.ts | 94 +- .../app-elements/src/providers/createApp.tsx | 22 +- packages/app-elements/src/styles/global.css | 1 - packages/app-elements/src/ui/atoms/A.test.tsx | 86 +- packages/app-elements/src/ui/atoms/A.tsx | 20 +- .../app-elements/src/ui/atoms/Alert.test.tsx | 16 +- packages/app-elements/src/ui/atoms/Alert.tsx | 30 +- .../app-elements/src/ui/atoms/Avatar.test.tsx | 48 +- packages/app-elements/src/ui/atoms/Avatar.tsx | 52 +- .../app-elements/src/ui/atoms/Avatar.utils.ts | 74 +- .../atoms/AvatarLetter/AvatarLetter.test.tsx | 18 +- .../ui/atoms/AvatarLetter/AvatarLetter.tsx | 32 +- .../src/ui/atoms/AvatarLetter/colors.test.ts | 18 +- .../src/ui/atoms/AvatarLetter/colors.ts | 63 +- .../src/ui/atoms/AvatarLetter/index.tsx | 2 +- .../src/ui/atoms/Badge/Badge.test.tsx | 22 +- .../app-elements/src/ui/atoms/Badge/Badge.tsx | 22 +- .../src/ui/atoms/Badge/badgeVariants.ts | 48 +- .../app-elements/src/ui/atoms/Badge/index.ts | 2 +- .../app-elements/src/ui/atoms/Button.test.tsx | 68 +- packages/app-elements/src/ui/atoms/Button.tsx | 18 +- .../src/ui/atoms/ButtonFilter.test.tsx | 50 +- .../src/ui/atoms/ButtonFilter.tsx | 46 +- .../src/ui/atoms/ButtonImageSelect.test.tsx | 28 +- .../src/ui/atoms/ButtonImageSelect.tsx | 26 +- .../app-elements/src/ui/atoms/Card.test.tsx | 68 +- packages/app-elements/src/ui/atoms/Card.tsx | 72 +- .../src/ui/atoms/CodeBlock.test.tsx | 32 +- .../app-elements/src/ui/atoms/CodeBlock.tsx | 54 +- .../src/ui/atoms/Container.test.tsx | 18 +- .../app-elements/src/ui/atoms/Container.tsx | 10 +- .../src/ui/atoms/CopyToClipboard.test.tsx | 48 +- .../src/ui/atoms/CopyToClipboard.tsx | 86 +- .../src/ui/atoms/EmptyState.test.tsx | 28 +- .../app-elements/src/ui/atoms/EmptyState.tsx | 28 +- .../app-elements/src/ui/atoms/Grid.test.tsx | 12 +- packages/app-elements/src/ui/atoms/Grid.tsx | 28 +- .../app-elements/src/ui/atoms/Hint.test.tsx | 24 +- packages/app-elements/src/ui/atoms/Hint.tsx | 14 +- .../app-elements/src/ui/atoms/Hr.test.tsx | 18 +- packages/app-elements/src/ui/atoms/Hr.tsx | 14 +- .../src/ui/atoms/Icon/Icon.test.tsx | 14 +- .../app-elements/src/ui/atoms/Icon/Icon.tsx | 10 +- .../app-elements/src/ui/atoms/Icon/icons.tsx | 264 +- .../app-elements/src/ui/atoms/Icon/index.ts | 2 +- .../ui/atoms/PageHeading/PageHeading.test.tsx | 88 +- .../src/ui/atoms/PageHeading/PageHeading.tsx | 58 +- .../PageHeading/PageHeadingToolbar.test.tsx | 100 +- .../atoms/PageHeading/PageHeadingToolbar.tsx | 37 +- .../src/ui/atoms/PageHeading/index.tsx | 2 +- .../src/ui/atoms/Pagination.test.tsx | 22 +- .../app-elements/src/ui/atoms/Pagination.tsx | 48 +- .../src/ui/atoms/Progress.test.tsx | 44 +- .../app-elements/src/ui/atoms/Progress.tsx | 26 +- .../src/ui/atoms/RadialProgress.test.tsx | 108 +- .../src/ui/atoms/RadialProgress.tsx | 67 +- .../src/ui/atoms/RemoveButton.test.tsx | 16 +- .../src/ui/atoms/RemoveButton.tsx | 18 +- .../app-elements/src/ui/atoms/ScrollToTop.tsx | 2 +- .../src/ui/atoms/Section.test.tsx | 76 +- .../app-elements/src/ui/atoms/Section.tsx | 33 +- .../src/ui/atoms/Skeleton.test.tsx | 64 +- .../app-elements/src/ui/atoms/Skeleton.tsx | 26 +- .../src/ui/atoms/SkeletonTemplate.test.tsx | 102 +- .../src/ui/atoms/SkeletonTemplate.tsx | 67 +- .../app-elements/src/ui/atoms/Spacer.test.tsx | 66 +- packages/app-elements/src/ui/atoms/Spacer.tsx | 104 +- .../app-elements/src/ui/atoms/Stack.test.tsx | 10 +- packages/app-elements/src/ui/atoms/Stack.tsx | 25 +- .../src/ui/atoms/StatusDot.test.tsx | 22 +- .../app-elements/src/ui/atoms/StatusDot.tsx | 16 +- .../src/ui/atoms/StatusIcon.test.tsx | 14 +- .../app-elements/src/ui/atoms/StatusIcon.tsx | 68 +- .../app-elements/src/ui/atoms/Steps.test.tsx | 84 +- packages/app-elements/src/ui/atoms/Steps.tsx | 54 +- .../src/ui/atoms/Table/Table.test.tsx | 32 +- .../app-elements/src/ui/atoms/Table/Table.tsx | 18 +- .../app-elements/src/ui/atoms/Table/Td.tsx | 14 +- .../app-elements/src/ui/atoms/Table/Th.tsx | 12 +- .../app-elements/src/ui/atoms/Table/Tr.tsx | 4 +- .../app-elements/src/ui/atoms/Table/index.tsx | 8 +- .../app-elements/src/ui/atoms/Tabs.test.tsx | 44 +- packages/app-elements/src/ui/atoms/Tabs.tsx | 61 +- .../app-elements/src/ui/atoms/Tag.test.tsx | 16 +- packages/app-elements/src/ui/atoms/Tag.tsx | 36 +- .../app-elements/src/ui/atoms/Text.test.tsx | 54 +- packages/app-elements/src/ui/atoms/Text.tsx | 82 +- .../src/ui/atoms/Tooltip.test.tsx | 26 +- .../app-elements/src/ui/atoms/Tooltip.tsx | 44 +- .../ActionButtons/ActionButtons.test.tsx | 54 +- .../composite/ActionButtons/ActionButtons.tsx | 40 +- .../src/ui/composite/ActionButtons/index.tsx | 2 +- .../app-elements/src/ui/composite/Address.tsx | 260 +- .../src/ui/composite/CardDialog.tsx | 45 +- .../ui/composite/Dropdown/Dropdown.test.tsx | 62 +- .../src/ui/composite/Dropdown/Dropdown.tsx | 75 +- .../ui/composite/Dropdown/DropdownDivider.tsx | 8 +- .../composite/Dropdown/DropdownItem.test.tsx | 40 +- .../ui/composite/Dropdown/DropdownItem.tsx | 52 +- .../ui/composite/Dropdown/DropdownMenu.tsx | 64 +- .../Dropdown/DropdownSearch.test.tsx | 44 +- .../ui/composite/Dropdown/DropdownSearch.tsx | 35 +- .../src/ui/composite/Dropdown/index.ts | 8 +- .../src/ui/composite/HomePageLayout.test.tsx | 40 +- .../src/ui/composite/HomePageLayout.tsx | 24 +- .../src/ui/composite/List.test.tsx | 54 +- .../app-elements/src/ui/composite/List.tsx | 55 +- .../src/ui/composite/ListDetails.test.tsx | 28 +- .../src/ui/composite/ListDetails.tsx | 33 +- .../src/ui/composite/ListDetailsItem.test.tsx | 22 +- .../src/ui/composite/ListDetailsItem.tsx | 44 +- .../src/ui/composite/ListItem.test.tsx | 42 +- .../src/ui/composite/ListItem.tsx | 110 +- .../src/ui/composite/PageError.test.tsx | 28 +- .../src/ui/composite/PageError.tsx | 14 +- .../src/ui/composite/PageLayout.test.tsx | 28 +- .../src/ui/composite/PageLayout.tsx | 44 +- .../src/ui/composite/PageSkeleton.test.tsx | 8 +- .../src/ui/composite/PageSkeleton.tsx | 43 +- .../src/ui/composite/Report.test.tsx | 62 +- .../app-elements/src/ui/composite/Report.tsx | 39 +- .../src/ui/composite/Routes/Routes.tsx | 70 +- .../src/ui/composite/Routes/index.tsx | 8 +- .../src/ui/composite/Routes/utils.test.ts | 76 +- .../src/ui/composite/Routes/utils.ts | 66 +- .../src/ui/composite/SearchBar.test.tsx | 74 +- .../src/ui/composite/SearchBar.tsx | 70 +- .../src/ui/composite/TableData.test.tsx | 110 +- .../src/ui/composite/TableData.tsx | 54 +- .../src/ui/composite/Timeline.test.tsx | 62 +- .../src/ui/composite/Timeline.tsx | 142 +- .../app-elements/src/ui/composite/Toast.tsx | 37 +- .../src/ui/composite/Toolbar.test.tsx | 82 +- .../app-elements/src/ui/composite/Toolbar.tsx | 51 +- .../src/ui/forms/CodeEditor/CodeEditor.tsx | 18 +- .../forms/CodeEditor/CodeEditorComponent.tsx | 142 +- .../ui/forms/CodeEditor/HookedCodeEditor.tsx | 14 +- .../fetchCoreResourcesSuggestions.test.ts | 146 +- .../fetchCoreResourcesSuggestions.ts | 130 +- .../src/ui/forms/CodeEditor/index.tsx | 8 +- .../src/ui/forms/Form/HookedForm.tsx | 10 +- .../app-elements/src/ui/forms/Form/index.tsx | 2 +- .../src/ui/forms/Input/HookedInput.tsx | 12 +- .../src/ui/forms/Input/Input.test.tsx | 28 +- .../app-elements/src/ui/forms/Input/Input.tsx | 50 +- .../app-elements/src/ui/forms/Input/index.tsx | 4 +- .../InputCheckbox/HookedInputCheckbox.tsx | 16 +- .../InputCheckbox/InputCheckbox.test.tsx | 28 +- .../ui/forms/InputCheckbox/InputCheckbox.tsx | 53 +- .../src/ui/forms/InputCheckbox/index.tsx | 6 +- .../HookedInputCheckboxGroup.tsx | 12 +- .../InputCheckboxGroup.test.tsx | 174 +- .../InputCheckboxGroup/InputCheckboxGroup.tsx | 58 +- .../InputCheckboxGroupItem.tsx | 30 +- .../src/ui/forms/InputCheckboxGroup/index.tsx | 8 +- .../ui/forms/InputCheckboxGroup/reducer.ts | 24 +- .../InputCurrency/HookedInputCurrency.tsx | 12 +- .../InputCurrency/InputCurrency.test.tsx | 192 +- .../ui/forms/InputCurrency/InputCurrency.tsx | 98 +- .../src/ui/forms/InputCurrency/index.ts | 8 +- .../src/ui/forms/InputCurrency/utils.test.ts | 40 +- .../src/ui/forms/InputCurrency/utils.ts | 26 +- .../src/ui/forms/InputCurrencyRange.test.tsx | 106 +- .../src/ui/forms/InputCurrencyRange.tsx | 57 +- .../ui/forms/InputDate/HookedInputDate.tsx | 16 +- .../src/ui/forms/InputDate/InputDate.tsx | 20 +- .../ui/forms/InputDate/InputDateComponent.css | 1 - .../InputDate/InputDateComponent.test.tsx | 52 +- .../ui/forms/InputDate/InputDateComponent.tsx | 54 +- .../src/ui/forms/InputDate/index.tsx | 6 +- .../InputDateRange/HookedInputDateRange.tsx | 28 +- .../InputDateRange/InputDateRange.test.tsx | 64 +- .../forms/InputDateRange/InputDateRange.tsx | 63 +- .../src/ui/forms/InputDateRange/index.tsx | 6 +- .../src/ui/forms/InputFeedback.test.tsx | 32 +- .../src/ui/forms/InputFeedback.tsx | 36 +- .../src/ui/forms/InputFile.test.tsx | 38 +- .../app-elements/src/ui/forms/InputFile.tsx | 46 +- .../src/ui/forms/InputJson.test.tsx | 48 +- .../app-elements/src/ui/forms/InputJson.tsx | 26 +- .../InputRadioGroup/HookedInputRadioGroup.tsx | 26 +- .../InputRadioGroup/InputRadioGroup.test.tsx | 116 +- .../forms/InputRadioGroup/InputRadioGroup.tsx | 81 +- .../src/ui/forms/InputRadioGroup/index.tsx | 6 +- .../ui/forms/InputResourceGroup/FullList.tsx | 73 +- .../HookedInputResourceGroup.tsx | 10 +- .../InputResourceGroup/InputResourceGroup.tsx | 130 +- .../src/ui/forms/InputResourceGroup/index.tsx | 10 +- .../ui/forms/InputResourceGroup/utils.test.ts | 68 +- .../src/ui/forms/InputResourceGroup/utils.ts | 28 +- .../ui/forms/InputSelect/AsyncComponent.tsx | 14 +- .../InputSelect/AsyncCreatableComponent.tsx | 14 +- .../forms/InputSelect/CreatableComponent.tsx | 22 +- .../InputSelect/GenericAsyncComponent.tsx | 42 +- .../forms/InputSelect/HookedInputSelect.tsx | 26 +- .../ui/forms/InputSelect/InputSelect.test.tsx | 114 +- .../src/ui/forms/InputSelect/InputSelect.tsx | 52 +- .../ui/forms/InputSelect/SelectComponent.tsx | 18 +- .../src/ui/forms/InputSelect/index.tsx | 12 +- .../src/ui/forms/InputSelect/overrides.tsx | 85 +- .../src/ui/forms/InputSelect/styles.ts | 140 +- .../src/ui/forms/InputSelect/utils.test.ts | 190 +- .../src/ui/forms/InputSelect/utils.ts | 26 +- .../HookedInputSimpleSelect.tsx | 12 +- .../InputSimpleSelect.test.tsx | 50 +- .../InputSimpleSelect/InputSimpleSelect.tsx | 16 +- .../src/ui/forms/InputSimpleSelect/index.tsx | 8 +- .../forms/InputSpinner/HookedInputSpinner.tsx | 12 +- .../forms/InputSpinner/InputSpinner.test.tsx | 72 +- .../ui/forms/InputSpinner/InputSpinner.tsx | 64 +- .../src/ui/forms/InputSpinner/index.tsx | 6 +- .../forms/InputSwitch/HookedInputSwitch.tsx | 10 +- .../ui/forms/InputSwitch/InputSwitch.test.tsx | 36 +- .../src/ui/forms/InputSwitch/InputSwitch.tsx | 30 +- .../src/ui/forms/InputSwitch/index.tsx | 6 +- .../InputTextArea/HookedInputTextArea.tsx | 10 +- .../InputTextArea/InputTextArea.test.tsx | 22 +- .../ui/forms/InputTextArea/InputTextArea.tsx | 18 +- .../src/ui/forms/InputTextArea/index.tsx | 6 +- .../HookedInputToggleButton.tsx | 14 +- .../InputToggleButton.test.tsx | 186 +- .../InputToggleButton/InputToggleButton.tsx | 44 +- .../src/ui/forms/InputToggleButton/index.tsx | 8 +- .../src/ui/forms/InputWrapper.test.tsx | 16 +- .../app-elements/src/ui/forms/Label.test.tsx | 18 +- packages/app-elements/src/ui/forms/Label.tsx | 11 +- packages/app-elements/src/ui/forms/Legend.tsx | 10 +- .../HookedMarketWithCurrencySelector.test.tsx | 60 +- .../HookedMarketWithCurrencySelector.tsx | 110 +- .../MarketWithCurrencySelector/index.tsx | 4 +- .../HookedValidationApiError.tsx | 28 +- .../ReactHookForm/HookedValidationError.tsx | 8 +- .../src/ui/forms/ReactHookForm/index.tsx | 8 +- .../ReactHookForm/setApiFormErrors.test.ts | 140 +- .../forms/ReactHookForm/setApiFormErrors.ts | 54 +- .../ReactHookForm/useValidationFeedback.ts | 14 +- .../RuleEngine/Action/ActionListItem.tsx | 73 +- .../forms/RuleEngine/Action/ActionValue.tsx | 64 +- .../src/ui/forms/RuleEngine/Action/index.tsx | 28 +- .../forms/RuleEngine/Condition/ArrayMatch.tsx | 0 .../Condition/ConditionListItem.tsx | 53 +- .../RuleEngine/Condition/ConditionMatcher.tsx | 193 +- .../RuleEngine/Condition/ConditionValue.tsx | 200 +- .../forms/RuleEngine/Condition/Connector.tsx | 0 .../RuleEngine/Condition/RangeInputs.tsx | 0 .../ValueComponents/InputArrayMatch.tsx | 60 +- .../ValueComponents/InputNumberRange.tsx | 43 +- .../ValueComponents/InputTextRange.tsx | 35 +- .../RuleEngine/Condition/dictionaries.ts | 0 .../ui/forms/RuleEngine/Condition/hooks.tsx | 8 +- .../ui/forms/RuleEngine/Condition/index.tsx | 45 +- .../ui/forms/RuleEngine/Condition/types.ts | 0 .../ui/forms/RuleEngine/Condition/utils.ts | 36 +- .../src/ui/forms/RuleEngine/RuleEngine.tsx | 17 +- .../forms/RuleEngine/RuleEngineComponent.tsx | 153 +- .../ui/forms/RuleEngine/RuleEngineContext.tsx | 65 +- .../src/ui/forms/RuleEngine/RuleName.tsx | 19 +- .../__snapshots__/index.test.tsx.snap | 2 + .../src/ui/forms/RuleEngine/index.test.tsx | 350 +- .../src/ui/forms/RuleEngine/index.tsx | 4 +- .../ui/forms/RuleEngine/schema.order_rules.ts | 248 +- .../ui/forms/RuleEngine/schema.price_rules.ts | 210 +- .../src/ui/forms/RuleEngine/utils.ts | 26 +- .../app-elements/src/ui/internals/FlexRow.tsx | 24 +- .../src/ui/internals/InputWrapper.tsx | 96 +- .../internals/InteractiveElement.className.ts | 91 +- .../app-elements/src/ui/internals/Overlay.tsx | 49 +- .../src/ui/internals/useCountryList.ts | 12 +- .../ResourceAddress/ResourceAddress.mocks.ts | 136 +- .../ResourceAddress/ResourceAddress.test.tsx | 142 +- .../ResourceAddress/ResourceAddress.tsx | 32 +- .../ResourceAddress/ResourceAddressForm.tsx | 52 +- .../ResourceAddressFormFields.tsx | 131 +- .../ui/resources/ResourceAddress/index.tsx | 10 +- .../useResourceAddressOverlay.tsx | 33 +- .../ResourceAttachments.tsx | 72 +- .../resources/ResourceAttachments/index.tsx | 4 +- .../ResourceDetails/ResourceDetails.tsx | 61 +- .../ResourceDetails/ResourceDetailsForm.tsx | 61 +- .../ui/resources/ResourceDetails/index.tsx | 2 +- .../ResourceDetails/useEditDetailsOverlay.tsx | 30 +- .../ResourceLineItems.mocks.ts | 704 +- .../ResourceLineItems.test.tsx | 256 +- .../ResourceLineItems/ResourceLineItems.tsx | 417 +- .../ui/resources/ResourceLineItems/index.tsx | 4 +- .../ui/resources/ResourceLineItems/types.ts | 2 +- .../ResourceListItem.mocks.ts | 880 +- .../ResourceListItem/ResourceListItem.tsx | 118 +- .../ui/resources/ResourceListItem/common.tsx | 18 +- .../ui/resources/ResourceListItem/index.tsx | 4 +- .../transformers/customers.tsx | 18 +- .../ResourceListItem/transformers/index.tsx | 12 +- .../ResourceListItem/transformers/orders.tsx | 60 +- .../transformers/promotions.tsx | 52 +- .../ResourceListItem/transformers/returns.tsx | 30 +- .../transformers/shipments.tsx | 46 +- .../transformers/skuListItem.tsx | 19 +- .../transformers/stockTransfers.tsx | 34 +- .../ui/resources/ResourceListItem/types.ts | 14 +- .../ResourceMetadata.test.tsx | 40 +- .../ResourceMetadata/ResourceMetadata.tsx | 77 +- .../ResourceMetadata/ResourceMetadataForm.tsx | 143 +- .../ui/resources/ResourceMetadata/index.tsx | 4 +- .../resources/ResourceMetadata/utils.test.ts | 38 +- .../ui/resources/ResourceMetadata/utils.ts | 6 +- .../ui/resources/ResourceOrderTimeline.tsx | 467 +- .../resources/ResourcePaymentMethod.mocks.tsx | 492 +- .../resources/ResourcePaymentMethod.test.tsx | 60 +- .../ui/resources/ResourcePaymentMethod.tsx | 138 +- .../ResourceShipmentParcels.mocks.tsx | 548 +- .../ResourceShipmentParcels.test.tsx | 144 +- .../ui/resources/ResourceShipmentParcels.tsx | 369 +- .../src/ui/resources/ResourceTags.tsx | 114 +- .../useResourceFilters/FieldCurrencyRange.tsx | 63 +- .../useResourceFilters/FieldOptions.tsx | 24 +- .../useResourceFilters/FieldTextSearch.tsx | 10 +- .../useResourceFilters/FieldTimeRange.tsx | 94 +- .../useResourceFilters/FiltersForm.tsx | 64 +- .../useResourceFilters/FiltersNav.test.tsx | 62 +- .../useResourceFilters/FiltersNav.tsx | 190 +- .../useResourceFilters/FiltersSearchBar.tsx | 32 +- .../adaptFormValuesToSdk.test.ts | 182 +- .../adaptFormValuesToSdk.ts | 76 +- .../adaptFormValuesToUrlQuery.test.ts | 104 +- .../adaptFormValuesToUrlQuery.ts | 34 +- .../adaptSdkToMetrics.test.ts | 218 +- .../useResourceFilters/adaptSdkToMetrics.ts | 160 +- .../adaptSdkToUrlQuery.test.ts | 56 +- .../useResourceFilters/adaptSdkToUrlQuery.ts | 20 +- .../adaptUrlQueryToFormValues.test.ts | 120 +- .../adaptUrlQueryToFormValues.ts | 72 +- .../adaptUrlQueryToSdk.test.ts | 86 +- .../useResourceFilters/adaptUrlQueryToSdk.ts | 14 +- .../adaptUrlQueryToUrlQuery.test.ts | 66 +- .../adaptUrlQueryToUrlQuery.ts | 12 +- .../resources/useResourceFilters/adapters.ts | 50 +- .../useResourceFilters/adapters.types.ts | 58 +- .../ui/resources/useResourceFilters/index.tsx | 4 +- .../useResourceFilters/mockedInstructions.ts | 212 +- .../useResourceFilters/timeUtils.test.ts | 52 +- .../resources/useResourceFilters/timeUtils.ts | 88 +- .../ui/resources/useResourceFilters/types.ts | 60 +- .../useResourceFilters/useResourceFilters.tsx | 106 +- .../useResourceFilters/utils.test.ts | 64 +- .../ui/resources/useResourceFilters/utils.ts | 34 +- .../VisibilityTrigger.test.tsx | 14 +- .../useResourceList/VisibilityTrigger.tsx | 8 +- .../adaptMetricsOrderToCore.test.ts | 158 +- .../adaptMetricsOrderToCore.ts | 122 +- .../ui/resources/useResourceList/index.tsx | 6 +- .../useResourceList/infiniteFetcher.ts | 34 +- .../useResourceList/metricsApiClient.ts | 110 +- .../ui/resources/useResourceList/reducer.ts | 48 +- .../useResourceList/useResourceList.test.tsx | 92 +- .../useResourceList/useResourceList.tsx | 215 +- .../resources/useResourceList/utils.test.tsx | 46 +- .../ui/resources/useResourceList/utils.tsx | 4 +- packages/app-elements/src/utils/array.ts | 6 +- .../app-elements/src/utils/children.test.tsx | 66 +- packages/app-elements/src/utils/children.ts | 38 +- .../src/utils/extractHeaders.test.ts | 66 +- .../app-elements/src/utils/extractHeaders.ts | 2 +- .../app-elements/src/utils/htmltags.test.ts | 16 +- packages/app-elements/src/utils/htmltags.ts | 10 +- .../app-elements/src/utils/pagination.test.ts | 126 +- packages/app-elements/src/utils/pagination.ts | 18 +- packages/app-elements/src/utils/text.test.ts | 64 +- packages/app-elements/src/utils/text.ts | 18 +- packages/app-elements/stylelint.config.js | 24 +- packages/app-elements/tailwind.config.cjs | 283 +- packages/app-elements/tsconfig.json | 46 +- packages/app-elements/tsconfig.node.json | 5 +- packages/app-elements/vite.config.js | 66 +- packages/docs/.eslintrc.cjs | 15 - packages/docs/.lintstagedrc.json | 8 +- .../docs/.storybook/addon-container/Tool.tsx | 21 +- .../.storybook/addon-container/constants.ts | 6 +- .../.storybook/addon-container/manager.tsx | 11 +- .../.storybook/addon-gh-repository/Tool.tsx | 11 +- .../addon-gh-repository/constants.ts | 6 +- .../addon-gh-repository/manager.tsx | 11 +- .../docs/.storybook/addon-version/Tool.tsx | 11 +- .../.storybook/addon-version/constants.ts | 8 +- .../docs/.storybook/addon-version/manager.tsx | 11 +- .../docs/.storybook/commercelayer.theme.ts | 12 +- packages/docs/.storybook/main.ts | 64 +- packages/docs/.storybook/manager.ts | 4 +- packages/docs/.storybook/preview.tsx | 108 +- packages/docs/package.json | 6 +- packages/docs/postcss.config.cjs | 4 +- packages/docs/public/mockServiceWorker.js | 58 +- packages/docs/public/storybook-preview.css | 126 +- packages/docs/src/components/CodeSample.tsx | 50 +- packages/docs/src/generate-abilities.mjs | 109 +- packages/docs/src/mocks/browser.js | 4 +- packages/docs/src/mocks/data/addresses.js | 84 +- packages/docs/src/mocks/data/adjustments.js | 46 +- packages/docs/src/mocks/data/bundles.js | 358 +- packages/docs/src/mocks/data/customers.js | 45 +- packages/docs/src/mocks/data/line_items.js | 190 +- packages/docs/src/mocks/data/markets.js | 426 +- packages/docs/src/mocks/data/orders.js | 4900 +- packages/docs/src/mocks/data/tags.js | 24 +- packages/docs/src/mocks/handlers.js | 18 +- packages/docs/src/stories/atoms/A.stories.tsx | 110 +- .../docs/src/stories/atoms/Alert.stories.tsx | 10 +- .../docs/src/stories/atoms/Avatar.stories.tsx | 50 +- .../stories/atoms/AvatarLetter.stories.tsx | 46 +- .../docs/src/stories/atoms/Badge.stories.tsx | 34 +- .../docs/src/stories/atoms/Button.stories.tsx | 62 +- .../stories/atoms/ButtonFilter.stories.tsx | 42 +- .../atoms/ButtonImageSelect.stories.tsx | 16 +- .../docs/src/stories/atoms/Card.stories.tsx | 90 +- .../src/stories/atoms/CodeBlock.stories.tsx | 62 +- .../src/stories/atoms/Container.stories.tsx | 10 +- .../stories/atoms/CopyToClipboard.stories.tsx | 22 +- .../src/stories/atoms/EmptyState.stories.tsx | 36 +- .../stories/atoms/ErrorBoundary.stories.tsx | 30 +- .../docs/src/stories/atoms/Grid.stories.tsx | 54 +- .../docs/src/stories/atoms/Hint.stories.tsx | 16 +- .../docs/src/stories/atoms/Hr.stories.tsx | 20 +- .../docs/src/stories/atoms/Icon.stories.tsx | 40 +- .../src/stories/atoms/PageHeading.stories.tsx | 60 +- .../src/stories/atoms/Pagination.stories.tsx | 14 +- .../src/stories/atoms/Progress.stories.tsx | 16 +- .../stories/atoms/RadialProgress.stories.tsx | 30 +- .../stories/atoms/RemoveButton.stories.tsx | 16 +- .../src/stories/atoms/Section.stories.tsx | 40 +- .../src/stories/atoms/Skeleton.stories.tsx | 32 +- .../atoms/SkeletonTemplate.stories.tsx | 105 +- .../docs/src/stories/atoms/Spacer.stories.tsx | 22 +- .../docs/src/stories/atoms/Stack.stories.tsx | 66 +- .../src/stories/atoms/StatusDot.stories.tsx | 10 +- .../src/stories/atoms/StatusIcon.stories.tsx | 92 +- .../docs/src/stories/atoms/Steps.stories.tsx | 96 +- .../docs/src/stories/atoms/Table.stories.tsx | 52 +- .../docs/src/stories/atoms/Tabs.stories.tsx | 32 +- .../docs/src/stories/atoms/Tag.stories.tsx | 8 +- .../docs/src/stories/atoms/Text.stories.tsx | 32 +- .../src/stories/atoms/Tooltip.stories.tsx | 56 +- .../composite/ActionButtons.stories.tsx | 42 +- .../src/stories/composite/Address.stories.tsx | 48 +- .../stories/composite/CardDialog.stories.tsx | 186 +- .../stories/composite/Dropdown.stories.tsx | 282 +- .../src/stories/composite/List.stories.tsx | 44 +- .../stories/composite/ListDetails.stories.tsx | 60 +- .../composite/ListDetailsItem.stories.tsx | 112 +- .../stories/composite/ListItem.stories.tsx | 106 +- .../stories/composite/PageLayout.stories.tsx | 165 +- .../composite/PageSkeleton.stories.tsx | 14 +- .../src/stories/composite/Report.stories.tsx | 51 +- .../stories/composite/SearchBar.stories.tsx | 30 +- .../stories/composite/TableData.stories.tsx | 86 +- .../stories/composite/Timeline.stories.tsx | 97 +- .../src/stories/composite/Toast.stories.tsx | 60 +- .../src/stories/composite/Toolbar.stories.tsx | 88 +- .../stories/examples/ListImports.stories.tsx | 80 +- .../examples/ListResources.stories.tsx | 74 +- .../src/stories/examples/ListSkus.stories.tsx | 51 +- .../stories/examples/OrderHistory.stories.tsx | 56 +- .../src/stories/examples/Panel.stories.tsx | 54 +- .../HookedCodeEditor.stories.tsx | 92 +- .../react-hook-form/HookedForm.stories.tsx | 52 +- .../react-hook-form/HookedInput.stories.tsx | 40 +- .../HookedInputCheckbox.stories.tsx | 82 +- .../HookedInputCheckboxGroup.stories.tsx | 132 +- .../HookedInputCurrency.stories.tsx | 34 +- .../HookedInputDate.stories.tsx | 36 +- .../HookedInputDateRange.stories.tsx | 36 +- .../HookedInputRadioGroup.stories.tsx | 166 +- .../HookedInputResource.stories.tsx | 50 +- .../HookedInputSelect.stories.tsx | 168 +- .../HookedInputSimpleSelect.stories.tsx | 84 +- .../HookedInputSpinner.stories.tsx | 38 +- .../HookedInputSwitch.stories.tsx | 58 +- .../HookedInputTextArea.stories.tsx | 32 +- .../HookedInputToggleButton.stories.tsx | 48 +- ...okedMarketWithCurrencySelector.stories.tsx | 76 +- .../stories/forms/ui/CodeEditor.stories.tsx | 154 +- .../src/stories/forms/ui/Input.stories.tsx | 104 +- .../forms/ui/InputCheckbox.stories.tsx | 90 +- .../forms/ui/InputCheckboxGroup.stories.tsx | 170 +- .../forms/ui/InputCurrency.stories.tsx | 66 +- .../forms/ui/InputCurrencyRange.stories.tsx | 20 +- .../stories/forms/ui/InputDate.stories.tsx | 50 +- .../forms/ui/InputDateRange.stories.tsx | 44 +- .../forms/ui/InputFeedback.stories.tsx | 18 +- .../stories/forms/ui/InputFile.stories.tsx | 40 +- .../stories/forms/ui/InputJson.stories.tsx | 22 +- .../forms/ui/InputRadioGroup.stories.tsx | 278 +- .../forms/ui/InputResourceGroup.stories.tsx | 90 +- .../stories/forms/ui/InputSelect.stories.tsx | 154 +- .../forms/ui/InputSimpleSelect.stories.tsx | 94 +- .../stories/forms/ui/InputSpinner.stories.tsx | 30 +- .../stories/forms/ui/InputSwitch.stories.tsx | 64 +- .../forms/ui/InputTextArea.stories.tsx | 38 +- .../forms/ui/InputToggleButton.stories.tsx | 72 +- .../stories/forms/ui/RuleEngine.stories.tsx | 403 +- .../004.CoreSdkProvider.stories.tsx | 102 +- .../005.I18NProvider.stories.tsx | 54 +- .../src/stories/hooks/useOverlay.stories.tsx | 35 +- .../resources/ResourceAddress.stories.tsx | 219 +- .../resources/ResourceAttachments.stories.tsx | 20 +- .../resources/ResourceDetails.stories.tsx | 30 +- .../resources/ResourceLineItems.stories.tsx | 128 +- .../resources/ResourceListItem.stories.tsx | 75 +- .../resources/ResourceMetadata.stories.tsx | 56 +- .../ResourceOrderTimeline.stories.tsx | 26 +- .../ResourcePaymentMethod.stories.tsx | 35 +- .../ResourceShipmentParcels.stories.tsx | 74 +- .../resources/ResourceTags.stories.tsx | 60 +- .../resources/useResourceFilters.stories.tsx | 102 +- .../resources/useResourceList.stories.tsx | 116 +- .../src/stories/utility/Currency.data.tsx | 30 +- .../docs/src/stories/utility/Date.data.tsx | 214 +- .../docs/src/stories/utility/Route.data.tsx | 78 +- packages/docs/tailwind.config.cjs | 6 +- packages/docs/tsconfig.json | 40 +- packages/docs/vite.config.js | 6 +- pnpm-lock.yaml | 2228 +- 612 files changed, 57455 insertions(+), 60376 deletions(-) create mode 100644 .lintstagedrc.json create mode 100644 biome.jsonc delete mode 100644 packages/app-elements/.eslintignore delete mode 100644 packages/app-elements/.eslintrc.cjs create mode 100644 packages/app-elements/src/ui/forms/RuleEngine/Condition/ArrayMatch.tsx create mode 100644 packages/app-elements/src/ui/forms/RuleEngine/Condition/Connector.tsx create mode 100644 packages/app-elements/src/ui/forms/RuleEngine/Condition/RangeInputs.tsx create mode 100644 packages/app-elements/src/ui/forms/RuleEngine/Condition/dictionaries.ts create mode 100644 packages/app-elements/src/ui/forms/RuleEngine/Condition/types.ts delete mode 100644 packages/docs/.eslintrc.cjs diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 000000000..4cfa3814c --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,3 @@ +{ + "*.{js,jsx,ts,tsx,json,jsonc}": ["pnpm lint:fix"] +} diff --git a/.ncurc.js b/.ncurc.js index 3f468b230..3ae7863ce 100644 --- a/.ncurc.js +++ b/.ncurc.js @@ -1,21 +1,20 @@ module.exports = { - dep: ['prod', 'dev', 'optional', 'packageManager'], + dep: ["prod", "dev", "optional", "packageManager"], deep: true, upgrade: true, - reject: [ - 'pnpm' - ], + reject: ["pnpm"], filterResults: (name, { upgradedVersionSemver }) => { if ( - name === '@types/node' && parseInt(upgradedVersionSemver?.major) >= 22 || - name === 'eslint' && parseInt(upgradedVersionSemver?.major) >= 9 || - name === 'tailwindcss' && parseInt(upgradedVersionSemver?.major) >= 4 || - name === 'zod' && parseInt(upgradedVersionSemver?.major) >= 4 || - name === '@hookform/resolvers' && parseInt(upgradedVersionSemver?.major) >= 4 + (name === "@types/node" && + parseInt(upgradedVersionSemver?.major) >= 22) || + (name === "tailwindcss" && parseInt(upgradedVersionSemver?.major) >= 4) || + (name === "zod" && parseInt(upgradedVersionSemver?.major) >= 4) || + (name === "@hookform/resolvers" && + parseInt(upgradedVersionSemver?.major) >= 4) ) { return false } return true - } + }, } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 851e1da53..30be46df9 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,10 +3,10 @@ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp // List of extensions which should be recommended for users of this workspace. "recommendations": [ - "dbaeumer.vscode-eslint", "bradlc.vscode-tailwindcss", - "unifiedjs.vscode-mdx" + "unifiedjs.vscode-mdx", + "biomejs.biome" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 05d7ae791..a29ce23db 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,47 +1,14 @@ { - // Enable ESLint - "eslint.validate": [ - "javascript", - "javascriptreact", - "typescript", - "typescriptreact" - ], - // disable formatOnSave for eslint files - "[javascript]": { - "editor.formatOnSave": false - }, - "[javascriptreact]": { - "editor.formatOnSave": false - }, - "[typescript]": { - "editor.formatOnSave": false - }, - "[typescriptreact]": { - "editor.formatOnSave": false - }, - // keep it enable for all other files + "typescript.tsdk": "node_modules/typescript/lib", + "biome.enabled": true, + "editor.defaultFormatter": "biomejs.biome", "editor.formatOnSave": true, - // this ensure ESLint autofix all issue (formatting is done by prettier loaded as eslint rules) "editor.codeActionsOnSave": { - "source.fixAll": "explicit", - "source.organizeImports": "explicit" - }, - // be sure vscode always uses TS version in local project - "typescript.tsdk": "node_modules/typescript/lib", - // enable auto formatting on json and json with comments - "[jsonc]": { - "editor.defaultFormatter": "vscode.json-language-features" + "quickfix.biome": "explicit", + "source.organizeImports.biome": "explicit" }, - "json.format.enable": true, "files.associations": { "*.css": "tailwindcss", // enforce usage of Tailwind extention to lint css files - "*.json": "jsonc" // allow comments on json files - }, - // to have stylelint and tailwind intellisense working properly - "css.validate": false, - "less.validate": false, - "scss.validate": false, - "testing.automaticallyOpenPeekView": "never", - // Enable experimental IntelliSense support for MDX files - "mdx.experimentalLanguageServer": true + "*.json": "jsonc" + } } diff --git a/biome.jsonc b/biome.jsonc new file mode 100644 index 000000000..39f2b83f8 --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,52 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.1.1/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "includes": [ + "**", + "!**/vendor.css", + "!node_modules", + "!packages/app-elements/dist", + "!packages/docs/dist" + ], + "maxSize": 1572864 + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "formatWithErrors": true + }, + "assist": { + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "useExhaustiveDependencies": "off" + }, + "performance": { + "noAccumulatingSpread": "off" + }, + "suspicious": { + "noExplicitAny": "off" + } + } + }, + "javascript": { + "formatter": { + "semicolons": "asNeeded" + } + } +} diff --git a/lerna.json b/lerna.json index bd83c59c1..966fdce1b 100644 --- a/lerna.json +++ b/lerna.json @@ -8,4 +8,4 @@ "preid": "beta" } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 6977b01d0..09e07f0e1 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "build:elements": "pnpm --stream --filter @commercelayer/app-elements build", "build:abilities": "pnpm --filter docs build:abilities", "build:docs": "pnpm --filter docs build", - "lint": "pnpm --stream -r lint", - "lint:fix": "pnpm -r lint:fix", + "lint": "pnpm biome check", + "lint:fix": "pnpm biome check --write", "test": "pnpm -r test", "test:watch": "pnpm --stream -r test:watch", "ts:check": "pnpm --stream -r ts:check", @@ -35,6 +35,7 @@ "packages/*" ], "devDependencies": { + "@biomejs/biome": "2.1.1", "husky": "^9.1.7", "lerna": "^8.2.3", "lint-staged": "^16.1.2" diff --git a/packages/app-elements/.eslintignore b/packages/app-elements/.eslintignore deleted file mode 100644 index ccc792934..000000000 --- a/packages/app-elements/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -**/core_resources.ts -**/core_public_resources.ts diff --git a/packages/app-elements/.eslintrc.cjs b/packages/app-elements/.eslintrc.cjs deleted file mode 100644 index 60f8d8aca..000000000 --- a/packages/app-elements/.eslintrc.cjs +++ /dev/null @@ -1,15 +0,0 @@ -const path = require('path') - -/** @type {import('eslint').Linter.Config} */ -module.exports = { - extends: ['@commercelayer/eslint-config-ts-react'], - parser: '@typescript-eslint/parser', - parserOptions: { - project: path.resolve(__dirname, 'tsconfig.json'), - ecmaFeatures: { - jsx: true - }, - ecmaVersion: 12, - sourceType: 'module' - } -} diff --git a/packages/app-elements/.lintstagedrc.json b/packages/app-elements/.lintstagedrc.json index a852fab2f..2c16a97e7 100644 --- a/packages/app-elements/.lintstagedrc.json +++ b/packages/app-elements/.lintstagedrc.json @@ -1,7 +1,7 @@ { "*.{js,jsx,ts,tsx}": [ "bash -c \"tsc -p ./tsconfig.json --noEmit\"", - "pnpm lint:fix", "bash -c \"pnpm test\"" - ] + ], + "*.{js,jsx,ts,tsx,json,jsonc}": ["pnpm lint:fix"] } diff --git a/packages/app-elements/package.json b/packages/app-elements/package.json index c32d33026..e363420c0 100644 --- a/packages/app-elements/package.json +++ b/packages/app-elements/package.json @@ -30,8 +30,7 @@ "build": "tsc && vite build && pnpm build:css-vendor && pnpm build:tailwind-cfg", "build:tailwind-cfg": "cp ./tailwind.config.cjs ./dist/tailwind.config.js", "build:css-vendor": "pnpm exec tailwindcss -i ./src/styles/vendor.css -o ./dist/vendor.css --minify", - "lint": "eslint src --ext .ts,.tsx", - "lint:fix": "eslint src --ext .ts,.tsx --fix", + "lint:fix": "pnpm biome check --write", "test": "vitest run", "test:watch": "vitest", "ts:check": "tsc --noEmit", @@ -71,7 +70,6 @@ "zod": "^3.25.76" }, "devDependencies": { - "@commercelayer/eslint-config-ts-react": "^2.2.0", "@hookform/resolvers": "^3.10.0", "@phosphor-icons/react": "v2.1.10", "@tailwindcss/forms": "^0.5.10", @@ -86,7 +84,6 @@ "cross-fetch": "^4.1.0", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", - "eslint": "^8.57.1", "jsdom": "^26.1.0", "msw": "^2.10.4", "postcss": "^8.5.6", diff --git a/packages/app-elements/postcss.config.cjs b/packages/app-elements/postcss.config.cjs index 85f717cc0..33ad091d2 100644 --- a/packages/app-elements/postcss.config.cjs +++ b/packages/app-elements/postcss.config.cjs @@ -1,6 +1,6 @@ module.exports = { plugins: { tailwindcss: {}, - autoprefixer: {} - } + autoprefixer: {}, + }, } diff --git a/packages/app-elements/react-testing-library.config.js b/packages/app-elements/react-testing-library.config.js index c44951a68..df6631eeb 100644 --- a/packages/app-elements/react-testing-library.config.js +++ b/packages/app-elements/react-testing-library.config.js @@ -1 +1 @@ -import '@testing-library/jest-dom' +import "@testing-library/jest-dom" diff --git a/packages/app-elements/src/dictionaries/customers.ts b/packages/app-elements/src/dictionaries/customers.ts index 7c8236d99..346768ba3 100644 --- a/packages/app-elements/src/dictionaries/customers.ts +++ b/packages/app-elements/src/dictionaries/customers.ts @@ -1,44 +1,44 @@ -import type { Customer } from '@commercelayer/sdk' -import { t } from 'i18next' -import type { DisplayStatus } from './types' +import type { Customer } from "@commercelayer/sdk" +import { t } from "i18next" +import type { DisplayStatus } from "./types" export interface CustomerDisplayStatus extends DisplayStatus {} export function getCustomerDisplayStatus( - customerObj: Customer + customerObj: Customer, ): CustomerDisplayStatus { switch (customerObj.status) { - case 'prospect': + case "prospect": return { - label: t('resources.customers.attributes.status.prospect'), - icon: 'chatCircle', - color: 'orange', - task: t('resources.customers.attributes.status.prospect') + label: t("resources.customers.attributes.status.prospect"), + icon: "chatCircle", + color: "orange", + task: t("resources.customers.attributes.status.prospect"), } - case 'acquired': + case "acquired": return { - label: t('resources.customers.attributes.status.acquired'), - icon: 'check', - color: 'orange', - task: t('resources.customers.attributes.status.acquired') + label: t("resources.customers.attributes.status.acquired"), + icon: "check", + color: "orange", + task: t("resources.customers.attributes.status.acquired"), } - case 'repeat': + case "repeat": return { - label: t('resources.customers.attributes.status.repeat'), - icon: 'arrowUpRight', - color: 'orange', - task: t('resources.customers.attributes.status.repeat') + label: t("resources.customers.attributes.status.repeat"), + icon: "arrowUpRight", + color: "orange", + task: t("resources.customers.attributes.status.repeat"), } } } -export function getCustomerStatusName(status: Customer['status']): string { +export function getCustomerStatusName(status: Customer["status"]): string { const dictionary: Record = { - prospect: t('resources.customers.attributes.status.prospect'), - acquired: t('resources.customers.attributes.status.acquired'), - repeat: t('resources.customers.attributes.status.repeat') + prospect: t("resources.customers.attributes.status.prospect"), + acquired: t("resources.customers.attributes.status.acquired"), + repeat: t("resources.customers.attributes.status.repeat"), } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/orders.ts b/packages/app-elements/src/dictionaries/orders.ts index 9f5360544..7876e2ac3 100644 --- a/packages/app-elements/src/dictionaries/orders.ts +++ b/packages/app-elements/src/dictionaries/orders.ts @@ -1,11 +1,11 @@ -import type { StatusIconProps } from '#ui/atoms/StatusIcon' -import type { Order } from '@commercelayer/sdk' -import { t } from 'i18next' -import type { DisplayStatus } from './types' +import type { Order } from "@commercelayer/sdk" +import { t } from "i18next" +import type { StatusIconProps } from "#ui/atoms/StatusIcon" +import type { DisplayStatus } from "./types" export interface OrderDisplayStatus extends DisplayStatus { label: string - icon: StatusIconProps['name'] - color: StatusIconProps['background'] + icon: StatusIconProps["name"] + color: StatusIconProps["background"] task?: string } @@ -13,222 +13,222 @@ export function getOrderDisplayStatus(order: Order): OrderDisplayStatus { const combinedStatus = `${order.status}:${order.payment_status}:${order.fulfillment_status}` as const - if (order.status === 'editing') { + if (order.status === "editing") { return { - label: t('resources.orders.attributes.status.editing'), - icon: 'pencilSimple', - color: 'orange', - task: t('resources.orders.attributes.status.editing') + label: t("resources.orders.attributes.status.editing"), + icon: "pencilSimple", + color: "orange", + task: t("resources.orders.attributes.status.editing"), } } switch (combinedStatus) { - case 'placed:authorized:unfulfilled': - case 'placed:authorized:not_required': - case 'placed:paid:unfulfilled': - case 'placed:paid:not_required': - case 'placed:partially_refunded:unfulfilled': - case 'placed:partially_refunded:not_required': - case 'placed:free:unfulfilled': - case 'placed:free:not_required': + case "placed:authorized:unfulfilled": + case "placed:authorized:not_required": + case "placed:paid:unfulfilled": + case "placed:paid:not_required": + case "placed:partially_refunded:unfulfilled": + case "placed:partially_refunded:not_required": + case "placed:free:unfulfilled": + case "placed:free:not_required": return { - label: t('resources.orders.attributes.status.placed'), - icon: 'arrowDown', - color: 'orange', - task: t('apps.orders.tasks.awaiting_approval') + label: t("resources.orders.attributes.status.placed"), + icon: "arrowDown", + color: "orange", + task: t("apps.orders.tasks.awaiting_approval"), } - case 'placed:unpaid:unfulfilled': + case "placed:unpaid:unfulfilled": return { - label: t('resources.orders.attributes.status.placed'), - icon: 'x', - color: 'red', - task: t('apps.orders.tasks.error_to_cancel') + label: t("resources.orders.attributes.status.placed"), + icon: "x", + color: "red", + task: t("apps.orders.tasks.error_to_cancel"), } - case 'approved:authorized:unfulfilled': - case 'approved:authorized:not_required': + case "approved:authorized:unfulfilled": + case "approved:authorized:not_required": return { - label: t('resources.orders.attributes.status.approved'), - icon: 'creditCard', - color: 'orange', - task: t('apps.orders.tasks.payment_to_capture') + label: t("resources.orders.attributes.status.approved"), + icon: "creditCard", + color: "orange", + task: t("apps.orders.tasks.payment_to_capture"), } - case 'approved:paid:in_progress': - case 'approved:partially_refunded:in_progress': - case 'approved:free:in_progress': + case "approved:paid:in_progress": + case "approved:partially_refunded:in_progress": + case "approved:free:in_progress": return { - label: t('apps.orders.display_status.in_progress'), - icon: 'arrowClockwise', - color: 'orange', - task: t('apps.orders.tasks.fulfillment_in_progress') + label: t("apps.orders.display_status.in_progress"), + icon: "arrowClockwise", + color: "orange", + task: t("apps.orders.tasks.fulfillment_in_progress"), } - case 'approved:authorized:in_progress': + case "approved:authorized:in_progress": return { - label: t('apps.orders.display_status.in_progress_manual'), - icon: 'arrowClockwise', - color: 'orange', - task: t('apps.orders.tasks.fulfillment_in_progress') + label: t("apps.orders.display_status.in_progress_manual"), + icon: "arrowClockwise", + color: "orange", + task: t("apps.orders.tasks.fulfillment_in_progress"), } - case 'approved:paid:fulfilled': + case "approved:paid:fulfilled": return { - label: t('resources.orders.attributes.fulfillment_status.fulfilled'), - icon: 'check', - color: 'green' + label: t("resources.orders.attributes.fulfillment_status.fulfilled"), + icon: "check", + color: "green", } // TODO: This could be a gift-card and what If i do return? - case 'approved:free:fulfilled': + case "approved:free:fulfilled": return { - label: t('resources.orders.attributes.fulfillment_status.fulfilled'), - icon: 'check', - color: 'green' + label: t("resources.orders.attributes.fulfillment_status.fulfilled"), + icon: "check", + color: "green", } - case 'approved:paid:not_required': - case 'approved:partially_refunded:not_required': + case "approved:paid:not_required": + case "approved:partially_refunded:not_required": return { - label: t('resources.orders.attributes.status.approved'), - icon: 'check', - color: 'green' + label: t("resources.orders.attributes.status.approved"), + icon: "check", + color: "green", } - case 'approved:free:not_required': + case "approved:free:not_required": return { - label: t('resources.orders.attributes.status.approved'), - icon: 'check', - color: 'green' + label: t("resources.orders.attributes.status.approved"), + icon: "check", + color: "green", } - case 'approved:partially_refunded:fulfilled': + case "approved:partially_refunded:fulfilled": return { label: t( - 'resources.orders.attributes.payment_status.partially_refunded' + "resources.orders.attributes.payment_status.partially_refunded", ), - icon: 'check', - color: 'green' + icon: "check", + color: "green", } - case 'cancelled:voided:unfulfilled': - case 'cancelled:refunded:unfulfilled': - case 'cancelled:refunded:not_required': - case 'cancelled:unpaid:unfulfilled': - case 'cancelled:free:unfulfilled': + case "cancelled:voided:unfulfilled": + case "cancelled:refunded:unfulfilled": + case "cancelled:refunded:not_required": + case "cancelled:unpaid:unfulfilled": + case "cancelled:free:unfulfilled": return { - label: t('resources.orders.attributes.status.cancelled'), - icon: 'x', - color: 'gray' + label: t("resources.orders.attributes.status.cancelled"), + icon: "x", + color: "gray", } - case 'cancelled:refunded:fulfilled': + case "cancelled:refunded:fulfilled": return { - label: t('resources.orders.attributes.status.cancelled'), - icon: 'x', - color: 'gray' + label: t("resources.orders.attributes.status.cancelled"), + icon: "x", + color: "gray", } - case 'pending:unpaid:unfulfilled': - case 'pending:authorized:unfulfilled': - case 'pending:free:unfulfilled': + case "pending:unpaid:unfulfilled": + case "pending:authorized:unfulfilled": + case "pending:free:unfulfilled": return { - label: t('resources.orders.attributes.status.pending'), - icon: 'shoppingBag', - color: 'white' + label: t("resources.orders.attributes.status.pending"), + icon: "shoppingBag", + color: "white", } default: return { - label: `${t('common.not_handled')}: (${combinedStatus})`, - icon: 'warning', - color: 'white' + label: `${t("common.not_handled")}: (${combinedStatus})`, + icon: "warning", + color: "white", } } } export function getOrderTransactionName( - type: NonNullable[number]['type'] + type: NonNullable[number]["type"], ): { pastTense: string; singular: string } { const pastTenseDictionary: Record = { authorizations: t( - 'resources.orders.attributes.payment_status.authorized' + "resources.orders.attributes.payment_status.authorized", ).toLowerCase(), - captures: t('apps.orders.details.payment_captured').toLowerCase(), + captures: t("apps.orders.details.payment_captured").toLowerCase(), refunds: t( - 'resources.orders.attributes.payment_status.refunded' + "resources.orders.attributes.payment_status.refunded", ).toLowerCase(), - voids: t('resources.orders.attributes.payment_status.voided').toLowerCase() + voids: t("resources.orders.attributes.payment_status.voided").toLowerCase(), } const singularDictionary: Record = { - authorizations: t('apps.orders.details.payment_authorization'), - captures: t('apps.orders.details.payment_capture'), - refunds: t('apps.orders.details.payment_refund'), - voids: t('apps.orders.details.payment_void') + authorizations: t("apps.orders.details.payment_authorization"), + captures: t("apps.orders.details.payment_capture"), + refunds: t("apps.orders.details.payment_refund"), + voids: t("apps.orders.details.payment_void"), } return { pastTense: pastTenseDictionary[type], - singular: singularDictionary[type] + singular: singularDictionary[type], } } -export function getOrderStatusName(status: Order['status']): string { +export function getOrderStatusName(status: Order["status"]): string { const dictionary: Record = { - approved: t('resources.orders.attributes.status.approved'), - cancelled: t('resources.orders.attributes.status.cancelled'), - draft: t('resources.orders.attributes.status.draft'), - editing: t('resources.orders.attributes.status.editing'), - pending: t('resources.orders.attributes.status.pending'), - placed: t('resources.orders.attributes.status.placed'), - placing: t('resources.orders.attributes.status.placing') + approved: t("resources.orders.attributes.status.approved"), + cancelled: t("resources.orders.attributes.status.cancelled"), + draft: t("resources.orders.attributes.status.draft"), + editing: t("resources.orders.attributes.status.editing"), + pending: t("resources.orders.attributes.status.pending"), + placed: t("resources.orders.attributes.status.placed"), + placing: t("resources.orders.attributes.status.placing"), } return dictionary[status] } export function getOrderPaymentStatusName( - status: Order['payment_status'] + status: Order["payment_status"], ): string { const dictionary: Record = { - authorized: t('resources.orders.attributes.payment_status.authorized'), - paid: t('resources.orders.attributes.payment_status.paid'), - unpaid: t('resources.orders.attributes.payment_status.unpaid'), - free: t('resources.orders.attributes.payment_status.free'), - voided: t('resources.orders.attributes.payment_status.voided'), - refunded: t('resources.orders.attributes.payment_status.refunded'), + authorized: t("resources.orders.attributes.payment_status.authorized"), + paid: t("resources.orders.attributes.payment_status.paid"), + unpaid: t("resources.orders.attributes.payment_status.unpaid"), + free: t("resources.orders.attributes.payment_status.free"), + voided: t("resources.orders.attributes.payment_status.voided"), + refunded: t("resources.orders.attributes.payment_status.refunded"), partially_authorized: t( - 'resources.orders.attributes.payment_status.partially_authorized' + "resources.orders.attributes.payment_status.partially_authorized", ), partially_paid: t( - 'resources.orders.attributes.payment_status.partially_paid' + "resources.orders.attributes.payment_status.partially_paid", ), partially_refunded: t( - 'resources.orders.attributes.payment_status.partially_refunded' + "resources.orders.attributes.payment_status.partially_refunded", ), partially_voided: t( - 'resources.orders.attributes.payment_status.partially_voided' - ) + "resources.orders.attributes.payment_status.partially_voided", + ), } return dictionary[status] } export function getOrderFulfillmentStatusName( - status: Order['fulfillment_status'] + status: Order["fulfillment_status"], ): string { const dictionary: Record = { unfulfilled: t( - 'resources.orders.attributes.fulfillment_status.unfulfilled' + "resources.orders.attributes.fulfillment_status.unfulfilled", ), in_progress: t( - 'resources.orders.attributes.fulfillment_status.in_progress' + "resources.orders.attributes.fulfillment_status.in_progress", ), - fulfilled: t('resources.orders.attributes.fulfillment_status.fulfilled'), + fulfilled: t("resources.orders.attributes.fulfillment_status.fulfilled"), not_required: t( - 'resources.orders.attributes.fulfillment_status.not_required' - ) + "resources.orders.attributes.fulfillment_status.not_required", + ), } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/promotions.ts b/packages/app-elements/src/dictionaries/promotions.ts index 9c99d86ae..8b6b3f420 100644 --- a/packages/app-elements/src/dictionaries/promotions.ts +++ b/packages/app-elements/src/dictionaries/promotions.ts @@ -1,27 +1,27 @@ -import { getEventDateInfo } from '#helpers/date' -import type { Promotion } from '@commercelayer/sdk' -import { t } from 'i18next' -import type { DisplayStatus } from './types' +import type { Promotion } from "@commercelayer/sdk" +import { t } from "i18next" +import { getEventDateInfo } from "#helpers/date" +import type { DisplayStatus } from "./types" interface PromotionDisplayStatus extends DisplayStatus { - status: 'disabled' | 'active' | 'upcoming' | 'expired' | 'used' + status: "disabled" | "active" | "upcoming" | "expired" | "used" } export function getPromotionDisplayStatus( - promotion: Omit + promotion: Omit, ): PromotionDisplayStatus { if (promotion.disabled_at != null) { return { - status: 'disabled', - label: t('resources.promotions.attributes.status.disabled'), - icon: 'minus', - color: 'lightGray' + status: "disabled", + label: t("resources.promotions.attributes.status.disabled"), + icon: "minus", + color: "lightGray", } } const eventDateInfo = getEventDateInfo({ startsAt: promotion.starts_at, - expiresAt: promotion.expires_at + expiresAt: promotion.expires_at, }) if ( @@ -29,36 +29,36 @@ export function getPromotionDisplayStatus( promotion.total_usage_count === promotion.total_usage_limit ) { return { - status: 'used', - label: t('resources.promotions.attributes.status.expired'), - icon: 'flag', - color: 'gray' + status: "used", + label: t("resources.promotions.attributes.status.expired"), + icon: "flag", + color: "gray", } } switch (eventDateInfo) { - case 'past': + case "past": return { - status: 'expired', - label: t('resources.promotions.attributes.status.expired'), - icon: 'flag', - color: 'gray' + status: "expired", + label: t("resources.promotions.attributes.status.expired"), + icon: "flag", + color: "gray", } - case 'upcoming': + case "upcoming": return { - status: 'upcoming', - label: t('apps.promotions.display_status.upcoming'), - icon: 'calendarBlank', - color: 'gray' + status: "upcoming", + label: t("apps.promotions.display_status.upcoming"), + icon: "calendarBlank", + color: "gray", } - case 'active': + case "active": return { - status: 'active', - label: t('resources.promotions.attributes.status.active'), - icon: 'pulse', - color: 'green' + status: "active", + label: t("resources.promotions.attributes.status.active"), + icon: "pulse", + color: "green", } } } diff --git a/packages/app-elements/src/dictionaries/returns.ts b/packages/app-elements/src/dictionaries/returns.ts index 16c047b79..11e0d71cb 100644 --- a/packages/app-elements/src/dictionaries/returns.ts +++ b/packages/app-elements/src/dictionaries/returns.ts @@ -1,88 +1,88 @@ -import { type StatusIconProps } from '#ui/atoms/StatusIcon' -import type { Return } from '@commercelayer/sdk' -import { t } from 'i18next' -import type { DisplayStatus } from './types' +import type { Return } from "@commercelayer/sdk" +import { t } from "i18next" +import type { StatusIconProps } from "#ui/atoms/StatusIcon" +import type { DisplayStatus } from "./types" export interface ReturnDisplayStatus extends DisplayStatus { label: string - icon: StatusIconProps['name'] - color: StatusIconProps['background'] + icon: StatusIconProps["name"] + color: StatusIconProps["background"] task?: string } export function getReturnDisplayStatus(returnObj: Return): ReturnDisplayStatus { switch (returnObj.status) { - case 'requested': + case "requested": return { - label: t('resources.returns.attributes.status.requested'), - icon: 'chatCircle', - color: 'orange', - task: t('resources.returns.attributes.status.requested') + label: t("resources.returns.attributes.status.requested"), + icon: "chatCircle", + color: "orange", + task: t("resources.returns.attributes.status.requested"), } - case 'approved': + case "approved": return { - label: t('resources.returns.attributes.status.approved'), - icon: 'check', - color: 'orange', - task: t('resources.returns.attributes.status.approved') + label: t("resources.returns.attributes.status.approved"), + icon: "check", + color: "orange", + task: t("resources.returns.attributes.status.approved"), } - case 'shipped': + case "shipped": return { - label: t('resources.returns.attributes.status.shipped'), - icon: 'arrowUpRight', - color: 'orange', - task: t('resources.returns.attributes.status.shipped') + label: t("resources.returns.attributes.status.shipped"), + icon: "arrowUpRight", + color: "orange", + task: t("resources.returns.attributes.status.shipped"), } - case 'received': + case "received": return { - label: t('resources.returns.attributes.status.received'), - icon: 'check', - color: 'green' + label: t("resources.returns.attributes.status.received"), + icon: "check", + color: "green", } - case 'cancelled': + case "cancelled": return { - label: t('resources.returns.attributes.status.cancelled'), - icon: 'x', - color: 'gray' + label: t("resources.returns.attributes.status.cancelled"), + icon: "x", + color: "gray", } - case 'rejected': + case "rejected": return { - label: t('resources.returns.attributes.status.rejected'), - icon: 'x', - color: 'red' + label: t("resources.returns.attributes.status.rejected"), + icon: "x", + color: "red", } - case 'refunded': + case "refunded": return { - label: t('resources.returns.attributes.status.refunded'), - icon: 'creditCard', - color: 'green' + label: t("resources.returns.attributes.status.refunded"), + icon: "creditCard", + color: "green", } default: return { - label: `${t('common.not_handled')}: (${returnObj.status})`, - icon: 'warning', - color: 'white' + label: `${t("common.not_handled")}: (${returnObj.status})`, + icon: "warning", + color: "white", } } } -export function getReturnStatusName(status: Return['status']): string { +export function getReturnStatusName(status: Return["status"]): string { const dictionary: Record = { - draft: t('resources.returns.attributes.status.draft'), - requested: t('resources.returns.attributes.status.requested'), - approved: t('resources.returns.attributes.status.approved'), - shipped: t('resources.returns.attributes.status.shipped'), - received: t('resources.returns.attributes.status.received'), - cancelled: t('resources.returns.attributes.status.cancelled'), - rejected: t('resources.returns.attributes.status.rejected'), - refunded: t('resources.returns.attributes.status.refunded') + draft: t("resources.returns.attributes.status.draft"), + requested: t("resources.returns.attributes.status.requested"), + approved: t("resources.returns.attributes.status.approved"), + shipped: t("resources.returns.attributes.status.shipped"), + received: t("resources.returns.attributes.status.received"), + cancelled: t("resources.returns.attributes.status.cancelled"), + rejected: t("resources.returns.attributes.status.rejected"), + refunded: t("resources.returns.attributes.status.refunded"), } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/shipments.ts b/packages/app-elements/src/dictionaries/shipments.ts index ce3325ac4..b84a24cc1 100644 --- a/packages/app-elements/src/dictionaries/shipments.ts +++ b/packages/app-elements/src/dictionaries/shipments.ts @@ -1,119 +1,119 @@ -import { type StatusIconProps } from '#ui/atoms/StatusIcon' -import type { Shipment } from '@commercelayer/sdk' -import { t } from 'i18next' -import type { DisplayStatus } from './types' +import type { Shipment } from "@commercelayer/sdk" +import { t } from "i18next" +import type { StatusIconProps } from "#ui/atoms/StatusIcon" +import type { DisplayStatus } from "./types" export interface ShipmentDisplayStatus extends DisplayStatus { label: string - icon: StatusIconProps['name'] - color: StatusIconProps['background'] + icon: StatusIconProps["name"] + color: StatusIconProps["background"] task?: string } export function getShipmentDisplayStatus( shipment: Shipment, - awaitingStockTransfer: boolean = false + awaitingStockTransfer: boolean = false, ): ShipmentDisplayStatus { const shipmentStatus = awaitingStockTransfer - ? 'awaiting_stock_transfer' + ? "awaiting_stock_transfer" : shipment.status switch (shipmentStatus) { - case 'upcoming': + case "upcoming": return { - label: t('resources.shipments.attributes.status.upcoming'), - icon: 'truck', - color: 'gray' + label: t("resources.shipments.attributes.status.upcoming"), + icon: "truck", + color: "gray", } - case 'cancelled': + case "cancelled": return { - label: t('resources.shipments.attributes.status.cancelled'), - icon: 'x', - color: 'gray' + label: t("resources.shipments.attributes.status.cancelled"), + icon: "x", + color: "gray", } - case 'draft': + case "draft": return { - label: t('resources.shipments.attributes.status.draft'), - icon: 'minus', - color: 'gray' + label: t("resources.shipments.attributes.status.draft"), + icon: "minus", + color: "gray", } - case 'on_hold': + case "on_hold": return { - label: t('resources.shipments.attributes.status.on_hold'), - icon: 'hourglass', - color: 'orange', - task: t('resources.shipments.attributes.status.on_hold') + label: t("resources.shipments.attributes.status.on_hold"), + icon: "hourglass", + color: "orange", + task: t("resources.shipments.attributes.status.on_hold"), } - case 'packing': + case "packing": return { - label: t('resources.shipments.attributes.status.packing'), - icon: 'package', - color: 'orange', - task: t('resources.shipments.attributes.status.packing') + label: t("resources.shipments.attributes.status.packing"), + icon: "package", + color: "orange", + task: t("resources.shipments.attributes.status.packing"), } - case 'picking': + case "picking": return { - label: t('resources.shipments.attributes.status.picking'), - icon: 'arrowDown', - color: 'orange', - task: t('resources.shipments.attributes.status.picking') + label: t("resources.shipments.attributes.status.picking"), + icon: "arrowDown", + color: "orange", + task: t("resources.shipments.attributes.status.picking"), } - case 'ready_to_ship': + case "ready_to_ship": return { - label: t('resources.shipments.attributes.status.ready_to_ship'), - icon: 'arrowUpRight', - color: 'orange', - task: t('resources.shipments.attributes.status.ready_to_ship') + label: t("resources.shipments.attributes.status.ready_to_ship"), + icon: "arrowUpRight", + color: "orange", + task: t("resources.shipments.attributes.status.ready_to_ship"), } - case 'shipped': + case "shipped": return { - label: t('resources.shipments.attributes.status.shipped'), - icon: 'arrowUpRight', - color: 'green' + label: t("resources.shipments.attributes.status.shipped"), + icon: "arrowUpRight", + color: "green", } - case 'delivered': + case "delivered": return { - label: t('resources.shipments.attributes.status.delivered'), - icon: 'check', - color: 'green' + label: t("resources.shipments.attributes.status.delivered"), + icon: "check", + color: "green", } - case 'awaiting_stock_transfer': + case "awaiting_stock_transfer": return { - label: t('apps.shipments.details.awaiting_stock_transfer'), - icon: 'hourglass', - color: 'orange', - task: 'Awaiting stock transfers' + label: t("apps.shipments.details.awaiting_stock_transfer"), + icon: "hourglass", + color: "orange", + task: "Awaiting stock transfers", } default: return { - label: `${t('common.not_handled')}: (${shipment.status})`, - icon: 'warning', - color: 'white' + label: `${t("common.not_handled")}: (${shipment.status})`, + icon: "warning", + color: "white", } } } -export function getShipmentStatusName(status: Shipment['status']): string { +export function getShipmentStatusName(status: Shipment["status"]): string { const dictionary: Record = { - draft: t('resources.shipments.attributes.status.draft'), - on_hold: t('resources.shipments.attributes.status.on_hold'), - upcoming: t('resources.shipments.attributes.status.upcoming'), - packing: t('resources.shipments.attributes.status.packing'), - picking: t('resources.shipments.attributes.status.picking'), - ready_to_ship: t('resources.shipments.attributes.status.ready_to_ship'), - shipped: t('resources.shipments.attributes.status.shipped'), - cancelled: t('resources.shipments.attributes.status.cancelled'), - delivered: t('resources.shipments.attributes.status.delivered') + draft: t("resources.shipments.attributes.status.draft"), + on_hold: t("resources.shipments.attributes.status.on_hold"), + upcoming: t("resources.shipments.attributes.status.upcoming"), + packing: t("resources.shipments.attributes.status.packing"), + picking: t("resources.shipments.attributes.status.picking"), + ready_to_ship: t("resources.shipments.attributes.status.ready_to_ship"), + shipped: t("resources.shipments.attributes.status.shipped"), + cancelled: t("resources.shipments.attributes.status.cancelled"), + delivered: t("resources.shipments.attributes.status.delivered"), } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/stockTransfers.ts b/packages/app-elements/src/dictionaries/stockTransfers.ts index 476abc32d..a884b496d 100644 --- a/packages/app-elements/src/dictionaries/stockTransfers.ts +++ b/packages/app-elements/src/dictionaries/stockTransfers.ts @@ -1,85 +1,85 @@ -import { type StatusIconProps } from '#ui/atoms/StatusIcon' -import type { StockTransfer } from '@commercelayer/sdk' -import { t } from 'i18next' -import type { DisplayStatus } from './types' +import type { StockTransfer } from "@commercelayer/sdk" +import { t } from "i18next" +import type { StatusIconProps } from "#ui/atoms/StatusIcon" +import type { DisplayStatus } from "./types" export interface StockTransferDisplayStatus extends DisplayStatus { label: string - icon: StatusIconProps['name'] - color: StatusIconProps['background'] + icon: StatusIconProps["name"] + color: StatusIconProps["background"] task?: string } export function getStockTransferDisplayStatus( - stockTransfer: StockTransfer + stockTransfer: StockTransfer, ): StockTransferDisplayStatus { switch (stockTransfer.status) { - case 'upcoming': + case "upcoming": return { - label: t('resources.stock_transfers.attributes.status.upcoming'), - icon: 'arrowUpRight', - color: 'orange', - task: t('resources.stock_transfers.attributes.status.upcoming') + label: t("resources.stock_transfers.attributes.status.upcoming"), + icon: "arrowUpRight", + color: "orange", + task: t("resources.stock_transfers.attributes.status.upcoming"), } - case 'on_hold': + case "on_hold": return { - label: t('resources.stock_transfers.attributes.status.on_hold'), - icon: 'hourglass', - color: 'orange', - task: t('resources.stock_transfers.attributes.status.on_hold') + label: t("resources.stock_transfers.attributes.status.on_hold"), + icon: "hourglass", + color: "orange", + task: t("resources.stock_transfers.attributes.status.on_hold"), } - case 'picking': + case "picking": return { - label: t('resources.stock_transfers.attributes.status.picking'), - icon: 'arrowDown', - color: 'orange', - task: t('resources.stock_transfers.attributes.status.picking') + label: t("resources.stock_transfers.attributes.status.picking"), + icon: "arrowDown", + color: "orange", + task: t("resources.stock_transfers.attributes.status.picking"), } - case 'in_transit': + case "in_transit": return { - label: t('resources.stock_transfers.attributes.status.in_transit'), - icon: 'arrowsLeftRight', - color: 'orange', - task: t('resources.stock_transfers.attributes.status.in_transit') + label: t("resources.stock_transfers.attributes.status.in_transit"), + icon: "arrowsLeftRight", + color: "orange", + task: t("resources.stock_transfers.attributes.status.in_transit"), } - case 'completed': + case "completed": return { - label: t('resources.stock_transfers.attributes.status.completed'), - icon: 'check', - color: 'green' + label: t("resources.stock_transfers.attributes.status.completed"), + icon: "check", + color: "green", } - case 'cancelled': + case "cancelled": return { - label: t('resources.stock_transfers.attributes.status.cancelled'), - icon: 'x', - color: 'gray' + label: t("resources.stock_transfers.attributes.status.cancelled"), + icon: "x", + color: "gray", } default: return { - label: `${t('common.not_handled')}: (${stockTransfer.status})`, - icon: 'warning', - color: 'white' + label: `${t("common.not_handled")}: (${stockTransfer.status})`, + icon: "warning", + color: "white", } } } export function getStockTransferStatusName( - status: StockTransfer['status'] + status: StockTransfer["status"], ): string { const dictionary: Record = { - cancelled: t('resources.stock_transfers.attributes.status.cancelled'), - completed: t('resources.stock_transfers.attributes.status.completed'), - draft: t('resources.stock_transfers.attributes.status.draft'), - in_transit: t('resources.stock_transfers.attributes.status.in_transit'), - on_hold: t('resources.stock_transfers.attributes.status.on_hold'), - picking: t('resources.stock_transfers.attributes.status.picking'), - upcoming: t('resources.stock_transfers.attributes.status.upcoming') + cancelled: t("resources.stock_transfers.attributes.status.cancelled"), + completed: t("resources.stock_transfers.attributes.status.completed"), + draft: t("resources.stock_transfers.attributes.status.draft"), + in_transit: t("resources.stock_transfers.attributes.status.in_transit"), + on_hold: t("resources.stock_transfers.attributes.status.on_hold"), + picking: t("resources.stock_transfers.attributes.status.picking"), + upcoming: t("resources.stock_transfers.attributes.status.upcoming"), } return dictionary[status] diff --git a/packages/app-elements/src/dictionaries/types.ts b/packages/app-elements/src/dictionaries/types.ts index cfeac58ed..46e1de53c 100644 --- a/packages/app-elements/src/dictionaries/types.ts +++ b/packages/app-elements/src/dictionaries/types.ts @@ -1,8 +1,8 @@ -import { type StatusIconProps } from '#ui/atoms/StatusIcon' +import type { StatusIconProps } from "#ui/atoms/StatusIcon" export interface DisplayStatus { label: string - icon: StatusIconProps['name'] - color: StatusIconProps['background'] + icon: StatusIconProps["name"] + color: StatusIconProps["background"] task?: string } diff --git a/packages/app-elements/src/helpers/appsNavigation.test.ts b/packages/app-elements/src/helpers/appsNavigation.test.ts index b53cb06d9..73165ac5e 100644 --- a/packages/app-elements/src/helpers/appsNavigation.test.ts +++ b/packages/app-elements/src/helpers/appsNavigation.test.ts @@ -1,204 +1,202 @@ -import { goBack, navigateTo, type BackToItem } from '#helpers/appsNavigation' +import { type BackToItem, goBack, navigateTo } from "#helpers/appsNavigation" // should always match version set in appsNavigation.ts const currentVersion = 0.2 const fakeEvent = { - preventDefault: () => undefined + preventDefault: () => undefined, } as unknown as React.MouseEvent function getSessionStorageItem(key: string): BackToItem { - return JSON.parse(sessionStorage.getItem(key) ?? '{}') + return JSON.parse(sessionStorage.getItem(key) ?? "{}") } const originalLocationObj = window.location function allowLocationMocks(): void { ;(window as typeof globalThis).location = { ...originalLocationObj, - origin: 'https://demo-store.commercelayer.app' + origin: "https://demo-store.commercelayer.app", } } -describe('navigateTo', () => { +describe("navigateTo", () => { beforeEach(() => { sessionStorage.clear() allowLocationMocks() vi.resetAllMocks() }) - test('should return an href string', () => { + test("should return an href string", () => { const navigate = navigateTo({ destination: { - app: 'customers', - resourceId: 'xBszDaQsAZ', - mode: 'live' - } + app: "customers", + resourceId: "xBszDaQsAZ", + mode: "live", + }, }) expect(navigate?.href).toBe( - 'https://demo-store.commercelayer.app/customers/list/xBszDaQsAZ?mode=live' + "https://demo-store.commercelayer.app/customers/list/xBszDaQsAZ?mode=live", ) }) - test('should return a valid onClick handlers for external app linking and store key in sessionStorage', () => { + test("should return a valid onClick handlers for external app linking and store key in sessionStorage", () => { // simulating we are on order details in app-order window.location.href = - 'https://demo-store.commercelayer.app/orders/list/' + "https://demo-store.commercelayer.app/orders/list/" window.location.assign = vi.fn() // we want to x-link to a customer details in app-customers const navigate = navigateTo({ destination: { - app: 'customers', - resourceId: '', - mode: 'test' - } + app: "customers", + resourceId: "", + mode: "test", + }, }) navigate?.onClick(fakeEvent) - // eslint-disable-next-line @typescript-eslint/unbound-method expect(window.location.assign).toBeCalledWith( - 'https://demo-store.commercelayer.app/customers/list/?mode=test' + "https://demo-store.commercelayer.app/customers/list/?mode=test", ) expect( getSessionStorageItem( - 'https://demo-store.commercelayer.app/customers/list/' - ) + "https://demo-store.commercelayer.app/customers/list/", + ), ).toEqual({ - url: 'https://demo-store.commercelayer.app/orders/list/', - version: currentVersion + url: "https://demo-store.commercelayer.app/orders/list/", + version: currentVersion, }) }) - test('should return a valid onClick handlers for internal app linking and store key in sessionStorage', () => { + test("should return a valid onClick handlers for internal app linking and store key in sessionStorage", () => { // simulating we are on orders filtered list in app-orders window.location.href = - 'https://demo-store.commercelayer.app/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress' + "https://demo-store.commercelayer.app/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress" const mockedSetLocation = vi.fn() // we want to x-link to customers app const navigate = navigateTo({ setLocation: mockedSetLocation, destination: { - app: 'orders', - resourceId: 'xbSzDaQsAZ' - } + app: "orders", + resourceId: "xbSzDaQsAZ", + }, }) navigate?.onClick(fakeEvent) // internal react router should be called - expect(mockedSetLocation).toBeCalledWith('/list/xbSzDaQsAZ') + expect(mockedSetLocation).toBeCalledWith("/list/xbSzDaQsAZ") // session storage key should contain url to go back to filtered list expect( getSessionStorageItem( - 'https://demo-store.commercelayer.app/orders/list/xbSzDaQsAZ' - ) + "https://demo-store.commercelayer.app/orders/list/xbSzDaQsAZ", + ), ).toEqual({ - url: 'https://demo-store.commercelayer.app/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress', - version: 0.2 + url: "https://demo-store.commercelayer.app/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress", + version: 0.2, }) }) - test('should return a valid onClick handlers for internal app linking when is self-hosted app', () => { + test("should return a valid onClick handlers for internal app linking when is self-hosted app", () => { // simulating we are on orders list in a custom app window.location.href = - 'https://my-custom-domain.com/list?archived_at_null=show' + "https://my-custom-domain.com/list?archived_at_null=show" const mockedSetLocation = vi.fn() // we want to x-link to customers app const navigate = navigateTo({ setLocation: mockedSetLocation, destination: { - app: 'orders', - resourceId: 'xbSzDaQsAZ' - } + app: "orders", + resourceId: "xbSzDaQsAZ", + }, }) navigate?.onClick(fakeEvent) // internal react router should be called - expect(mockedSetLocation).toBeCalledWith('/list/xbSzDaQsAZ') + expect(mockedSetLocation).toBeCalledWith("/list/xbSzDaQsAZ") }) - test('should return null for external app linking when app is custom (self-hosted)', () => { + test("should return null for external app linking when app is custom (self-hosted)", () => { // simulating we are on orders list in a custom app window.location.href = - 'https://my-custom-domain.com/list?archived_at_null=show' + "https://my-custom-domain.com/list?archived_at_null=show" // @ts-expect-error we want to mock window location.origin - window.location.origin = 'https://my-custom-domain.com' + window.location.origin = "https://my-custom-domain.com" // we want to x-link to customers app const navigate = navigateTo({ destination: { - app: 'customers', - resourceId: 'xbSzDaQsAZ', - mode: 'test' - } + app: "customers", + resourceId: "xbSzDaQsAZ", + mode: "test", + }, }) expect(navigate).toBe(null) }) }) -describe('goBack', () => { - test('should go back to default provided path when sessionStorage is empty', () => { +describe("goBack", () => { + test("should go back to default provided path when sessionStorage is empty", () => { const mockedSetLocation = vi.fn() goBack({ - defaultRelativePath: '/list', - setLocation: mockedSetLocation + defaultRelativePath: "/list", + setLocation: mockedSetLocation, }) - expect(mockedSetLocation).toBeCalledWith('/list') + expect(mockedSetLocation).toBeCalledWith("/list") }) - test('should go back to url in session storage when found (internal linking)', () => { + test("should go back to url in session storage when found (internal linking)", () => { const mockedSetLocation = vi.fn() location.href = - 'https://demo-store.commercelayer.app/orders/list/xbSzDaQsAZ' + "https://demo-store.commercelayer.app/orders/list/xbSzDaQsAZ" sessionStorage.setItem( - 'https://demo-store.commercelayer.app/orders/list/xbSzDaQsAZ', + "https://demo-store.commercelayer.app/orders/list/xbSzDaQsAZ", JSON.stringify({ - url: 'https://demo-store.commercelayer.app/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress', - version: currentVersion - }) + url: "https://demo-store.commercelayer.app/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress", + version: currentVersion, + }), ) goBack({ - defaultRelativePath: '/list', - setLocation: mockedSetLocation + defaultRelativePath: "/list", + setLocation: mockedSetLocation, }) expect(mockedSetLocation).toBeCalledWith( - '/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress' + "/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress", ) }) - test('should go back to url in session storage when found (cross app linking)', () => { + test("should go back to url in session storage when found (cross app linking)", () => { window.location.assign = vi.fn() location.href = - 'https://demo-store.commercelayer.app/customers/list/customerId' + "https://demo-store.commercelayer.app/customers/list/customerId" sessionStorage.setItem( - 'https://demo-store.commercelayer.app/customers/list/customerId', + "https://demo-store.commercelayer.app/customers/list/customerId", JSON.stringify({ - url: 'https://demo-store.commercelayer.app/order/list/xbSzDaQsAZ', - version: currentVersion - }) + url: "https://demo-store.commercelayer.app/order/list/xbSzDaQsAZ", + version: currentVersion, + }), ) goBack({ - defaultRelativePath: '/list', - setLocation: () => undefined + defaultRelativePath: "/list", + setLocation: () => undefined, }) - // eslint-disable-next-line @typescript-eslint/unbound-method expect(window.location.assign).toBeCalledWith( - 'https://demo-store.commercelayer.app/order/list/xbSzDaQsAZ' + "https://demo-store.commercelayer.app/order/list/xbSzDaQsAZ", ) }) }) -describe('navigateTo - When in dashboard', () => { +describe("navigateTo - When in dashboard", () => { beforeEach(() => { sessionStorage.clear() allowLocationMocks() @@ -209,11 +207,11 @@ describe('navigateTo - When in dashboard', () => { vi.resetAllMocks() }) - test('should return a valid onClick handlers ans store key in sessionStorage when navigating to another app', () => { + test("should return a valid onClick handlers ans store key in sessionStorage when navigating to another app", () => { // simulating we are on order details in app-order // @ts-expect-error we want to mock window.location.origin - window.location.origin = 'https://dashboard.commercelayer.io' - window.location.pathname = '/test/demo-store/hub/orders/list/' + window.location.origin = "https://dashboard.commercelayer.io" + window.location.pathname = "/test/demo-store/hub/orders/list/" window.location.href = `${window.location.origin}${window.location.pathname}` window.location.assign = vi.fn() @@ -221,33 +219,32 @@ describe('navigateTo - When in dashboard', () => { // we want to x-link to a customer details in app-customers const navigate = navigateTo({ destination: { - app: 'customers', - resourceId: '', - mode: 'test' - } + app: "customers", + resourceId: "", + mode: "test", + }, }) navigate?.onClick(fakeEvent) - // eslint-disable-next-line @typescript-eslint/unbound-method expect(window.location.assign).toBeCalledWith( - 'https://dashboard.commercelayer.io/test/demo-store/hub/customers/list/?mode=test' + "https://dashboard.commercelayer.io/test/demo-store/hub/customers/list/?mode=test", ) expect( getSessionStorageItem( - 'https://dashboard.commercelayer.io/test/demo-store/hub/customers/list/' - ) + "https://dashboard.commercelayer.io/test/demo-store/hub/customers/list/", + ), ).toEqual({ - url: 'https://dashboard.commercelayer.io/test/demo-store/hub/orders/list/', - version: currentVersion + url: "https://dashboard.commercelayer.io/test/demo-store/hub/orders/list/", + version: currentVersion, }) }) - test('should return a valid onClick handlers and store key in sessionStorage when navigating internally from list to details', () => { + test("should return a valid onClick handlers and store key in sessionStorage when navigating internally from list to details", () => { // @ts-expect-error we want to mock window.location.origin - window.location.origin = 'https://dashboard.commercelayer.io' + window.location.origin = "https://dashboard.commercelayer.io" window.location.pathname = - '/test/demo-store/hub/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress' + "/test/demo-store/hub/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress" // simulating we are on orders filtered list in app-orders window.location.href = `${window.location.origin}${window.location.pathname}` @@ -257,29 +254,29 @@ describe('navigateTo - When in dashboard', () => { const navigate = navigateTo({ setLocation: mockedSetLocation, destination: { - app: 'orders', - resourceId: 'xbSzDaQsAZ' - } + app: "orders", + resourceId: "xbSzDaQsAZ", + }, }) navigate?.onClick(fakeEvent) // internal react router should be called - expect(mockedSetLocation).toBeCalledWith('/list/xbSzDaQsAZ') + expect(mockedSetLocation).toBeCalledWith("/list/xbSzDaQsAZ") // session storage key should contain url to go back to filtered list expect( getSessionStorageItem( - 'https://dashboard.commercelayer.io/test/demo-store/hub/orders/list/xbSzDaQsAZ' - ) + "https://dashboard.commercelayer.io/test/demo-store/hub/orders/list/xbSzDaQsAZ", + ), ).toEqual({ - url: 'https://dashboard.commercelayer.io/test/demo-store/hub/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress', - version: 0.2 + url: "https://dashboard.commercelayer.io/test/demo-store/hub/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress", + version: 0.2, }) }) }) -describe('goBack - When in dashboard', () => { +describe("goBack - When in dashboard", () => { beforeEach(() => { sessionStorage.clear() allowLocationMocks() @@ -290,53 +287,52 @@ describe('goBack - When in dashboard', () => { vi.resetAllMocks() }) - test('should go back to url in session storage when found (internal linking)', () => { + test("should go back to url in session storage when found (internal linking)", () => { const mockedSetLocation = vi.fn() // @ts-expect-error we want to mock window.location.origin - window.location.origin = 'https://dashboard.commercelayer.io' - window.location.pathname = '/test/demo-store/hub/orders/list/xbSzDaQsAZ' + window.location.origin = "https://dashboard.commercelayer.io" + window.location.pathname = "/test/demo-store/hub/orders/list/xbSzDaQsAZ" window.location.href = `${window.location.origin}${window.location.pathname}` sessionStorage.setItem( - 'https://dashboard.commercelayer.io/test/demo-store/hub/orders/list/xbSzDaQsAZ', + "https://dashboard.commercelayer.io/test/demo-store/hub/orders/list/xbSzDaQsAZ", JSON.stringify({ - url: 'https://dashboard.commercelayer.io/test/demo-store/hub/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress', - version: currentVersion - }) + url: "https://dashboard.commercelayer.io/test/demo-store/hub/orders/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress", + version: currentVersion, + }), ) goBack({ - defaultRelativePath: '/list', - setLocation: mockedSetLocation + defaultRelativePath: "/list", + setLocation: mockedSetLocation, }) expect(mockedSetLocation).toBeCalledWith( - '/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress' + "/list?archived_at_null=show&fulfillment_status_in=in_progress&status_in=approved&viewTitle=Fulfillment+in+progress", ) }) - test('should go back to url in session storage when found (cross app linking)', () => { + test("should go back to url in session storage when found (cross app linking)", () => { // @ts-expect-error we want to mock window.location.origin - window.location.origin = 'https://dashboard.commercelayer.io' - window.location.pathname = '/test/demo-store/hub/customers/list/customerId' + window.location.origin = "https://dashboard.commercelayer.io" + window.location.pathname = "/test/demo-store/hub/customers/list/customerId" window.location.href = `${window.location.origin}${window.location.pathname}` window.location.assign = vi.fn() sessionStorage.setItem( - 'https://dashboard.commercelayer.io/test/demo-store/hub/customers/list/customerId', + "https://dashboard.commercelayer.io/test/demo-store/hub/customers/list/customerId", JSON.stringify({ - url: 'https://dashboard.commercelayer.io/test/demo-store/hub/orders/list/xbSzDaQsAZ', - version: currentVersion - }) + url: "https://dashboard.commercelayer.io/test/demo-store/hub/orders/list/xbSzDaQsAZ", + version: currentVersion, + }), ) goBack({ - defaultRelativePath: '/list', - setLocation: () => undefined + defaultRelativePath: "/list", + setLocation: () => undefined, }) - // eslint-disable-next-line @typescript-eslint/unbound-method expect(window.location.assign).toBeCalledWith( - 'https://dashboard.commercelayer.io/test/demo-store/hub/orders/list/xbSzDaQsAZ' + "https://dashboard.commercelayer.io/test/demo-store/hub/orders/list/xbSzDaQsAZ", ) }) }) diff --git a/packages/app-elements/src/helpers/appsNavigation.ts b/packages/app-elements/src/helpers/appsNavigation.ts index c75aaf4d9..3467b68de 100644 --- a/packages/app-elements/src/helpers/appsNavigation.ts +++ b/packages/app-elements/src/helpers/appsNavigation.ts @@ -1,5 +1,5 @@ -import { type TokenProviderClAppSlug } from '#providers/TokenProvider/types' -import isEmpty from 'lodash-es/isEmpty' +import isEmpty from "lodash-es/isEmpty" +import type { TokenProviderClAppSlug } from "#providers/TokenProvider/types" const currentVersion = 0.2 @@ -21,7 +21,7 @@ function getPersistentItem(): BackToItem | undefined { const itemName = location.href try { const item = JSON.parse( - sessionStorage.getItem(itemName) ?? '{}' + sessionStorage.getItem(itemName) ?? "{}", ) as BackToItem if (item.version === currentVersion) { return item @@ -42,8 +42,8 @@ function setPersistentItem({ destination }: { destination: string }): void { destination, JSON.stringify({ url: window.location.href, - version: currentVersion - }) + version: currentVersion, + }), ) } @@ -51,7 +51,7 @@ function setPersistentItem({ destination }: { destination: string }): void { * Returns `true` if we are running apps within the Commerce Layer dashboard. */ function isInDashboard(): boolean { - return window.location.origin.includes('https://dashboard.commercelayer.') + return window.location.origin.includes("https://dashboard.commercelayer.") } /** @@ -63,7 +63,7 @@ function urlIsForSameApp(url: string): boolean { const urlObj = new URL(url) const [appSlug] = urlObj.pathname - .split('/') + .split("/") .filter((p) => !isEmpty(p)) // when isInDashboard pathname is `/test/demo-store/hub/orders/list/xbSzDaQsAZ` // when is standalone we have only `/orders/list/xbSzDaQsAZ ` @@ -71,7 +71,7 @@ function urlIsForSameApp(url: string): boolean { .slice(isInDashboard() ? 3 : 0) if (appSlug === undefined) { - throw new Error('Cannot access to the application slug.') + throw new Error("Cannot access to the application slug.") } return `${urlObj.hostname}/${appSlug}` @@ -96,10 +96,10 @@ function getRelativePath(url: string): string { // when in dashboard pathname is `/test/demo-store/hub/orders/list/qfgDgXszab`, so we need to to remove 4 parts to reach `/list/qfgDgXszab` // when is standalone pathname is `/orders/list/qfgDgXszab?foo=bar` so we need to remove 1 part to reach `/list/qfgDgXszab?foo=bar` const relativePath = urlObj.pathname - .split('/') + .split("/") .filter((p) => !isEmpty(p)) .slice(isInDashboard() ? 4 : 1) - .join('/') + .join("/") return isEmpty(urlObj.search) ? `/${relativePath}` @@ -111,7 +111,7 @@ function getRelativePath(url: string): string { */ export function goBack({ setLocation, - defaultRelativePath + defaultRelativePath, }: { /** * React router's history.push method, this is used when linking internal app pages. @@ -173,7 +173,7 @@ interface NavigateToExternalParams { /** * required when linking to another app, it indicates if the destination app should be opened in test or live mode */ - mode: 'test' | 'live' + mode: "test" | "live" } } @@ -182,33 +182,33 @@ interface NavigateToExternalParams { * to be able to navigate back to it with the `goBack` function. */ export function navigateTo( - params: NavigateToInternalParams | NavigateToExternalParams + params: NavigateToInternalParams | NavigateToExternalParams, ): { href: string onClick: ( e: React.MouseEvent< HTMLAnchorElement | HTMLDivElement | HTMLButtonElement, MouseEvent - > + >, ) => void } | null { const pathname = isInDashboard() ? `/${location.pathname - .split('/') + .split("/") .filter((p) => !isEmpty(p)) .slice(0, 3) - .join('/')}/` - : '/' + .join("/")}/` + : "/" const destinationFullUrl = `${window.location.origin}${pathname}${ params.destination.app - }/list/${params.destination.resourceId ?? ''}` + }/list/${params.destination.resourceId ?? ""}` // cross linking is allowed only for Commerce Layer hosted apps. It's disabled for custom (self-hosted) apps. const isClHostedApp = isInDashboard() || - window.location.origin.includes('commercelayer.app') || - window.location.origin.includes('//localhost:') + window.location.origin.includes("commercelayer.app") || + window.location.origin.includes("//localhost:") if (!isNavigateToInternalParams(params) && !isClHostedApp) { return null } @@ -221,7 +221,7 @@ export function navigateTo( e: React.MouseEvent< HTMLAnchorElement | HTMLDivElement | HTMLButtonElement, MouseEvent - > + >, ) => { if (e.ctrlKey || e.metaKey) { // allow to open link in a new tab with ctrl+click or cmd+click @@ -234,14 +234,14 @@ export function navigateTo( return } window.location.assign( - `${destinationFullUrl}?mode=${params.destination.mode}` + `${destinationFullUrl}?mode=${params.destination.mode}`, ) - } + }, } } function isNavigateToInternalParams( - params: NavigateToInternalParams | NavigateToExternalParams + params: NavigateToInternalParams | NavigateToExternalParams, ): params is NavigateToInternalParams { - return 'setLocation' in params + return "setLocation" in params } diff --git a/packages/app-elements/src/helpers/attachments.ts b/packages/app-elements/src/helpers/attachments.ts index df3ee5884..b76f3f338 100644 --- a/packages/app-elements/src/helpers/attachments.ts +++ b/packages/app-elements/src/helpers/attachments.ts @@ -1,21 +1,21 @@ -import { type Attachment } from '@commercelayer/sdk' -import isEmpty from 'lodash-es/isEmpty' -import { type SetNonNullable, type SetRequired } from 'type-fest' +import type { Attachment } from "@commercelayer/sdk" +import isEmpty from "lodash-es/isEmpty" +import type { SetNonNullable, SetRequired } from "type-fest" export const referenceOrigins = { - appOrdersNote: 'app-orders--note', - appOrdersRefundNote: 'app-orders--refund-note', - appShipmentsNote: 'app-shipments--note' + appOrdersNote: "app-orders--note", + appOrdersRefundNote: "app-orders--refund-note", + appShipmentsNote: "app-shipments--note", } as const export function isAttachmentValidNote( attachment: Attachment, validReferenceOrigins: Array< (typeof referenceOrigins)[keyof typeof referenceOrigins] - > + >, ): attachment is SetNonNullable< - SetRequired, - 'description' | 'reference_origin' + SetRequired, + "description" | "reference_origin" > { if ( attachment.reference_origin == null || @@ -26,7 +26,7 @@ export function isAttachmentValidNote( return ( validReferenceOrigins.includes( - attachment.reference_origin as (typeof validReferenceOrigins)[number] + attachment.reference_origin as (typeof validReferenceOrigins)[number], ) && attachment.description != null ) } diff --git a/packages/app-elements/src/helpers/currencies.test.ts b/packages/app-elements/src/helpers/currencies.test.ts index a7d63e81f..f9708cd21 100644 --- a/packages/app-elements/src/helpers/currencies.test.ts +++ b/packages/app-elements/src/helpers/currencies.test.ts @@ -1,28 +1,28 @@ -import { currencyInputSelectOptions } from '#helpers/currencies' +import { currencyInputSelectOptions } from "#helpers/currencies" -describe('currencyInputSelectOptions', () => { - test('should return two groups of currencies', () => { +describe("currencyInputSelectOptions", () => { + test("should return two groups of currencies", () => { expect(currencyInputSelectOptions.length).toBe(2) }) - test('both groups should have no label', () => { + test("both groups should have no label", () => { expect( - currencyInputSelectOptions.every((group) => group.label == null) + currencyInputSelectOptions.every((group) => group.label == null), ).toBe(true) }) - test('first group should return USD, EUR, GBP only', () => { + test("first group should return USD, EUR, GBP only", () => { expect(currencyInputSelectOptions[0]).toEqual({ label: undefined, options: [ - { label: 'USD', value: 'USD' }, - { label: 'EUR', value: 'EUR' }, - { label: 'GBP', value: 'GBP' } - ] + { label: "USD", value: "USD" }, + { label: "EUR", value: "EUR" }, + { label: "GBP", value: "GBP" }, + ], }) }) - test('second group should not contain the top currencies from the first group', () => { + test("second group should not contain the top currencies from the first group", () => { const topCurrencies = currencyInputSelectOptions[0]?.options.map((o) => o.value as string) ?? [] const otherCurrencies = @@ -30,8 +30,8 @@ describe('currencyInputSelectOptions', () => { expect( otherCurrencies.some((currencyCode) => - topCurrencies.includes(currencyCode) - ) + topCurrencies.includes(currencyCode), + ), ).toBe(false) }) }) diff --git a/packages/app-elements/src/helpers/currencies.ts b/packages/app-elements/src/helpers/currencies.ts index e6e79540a..b2098bbd9 100644 --- a/packages/app-elements/src/helpers/currencies.ts +++ b/packages/app-elements/src/helpers/currencies.ts @@ -1,4 +1,4 @@ -import { type GroupedSelectValues } from '#ui/forms/InputSelect/InputSelect' +import type { GroupedSelectValues } from "#ui/forms/InputSelect/InputSelect" export interface Currency { symbol: string subunit_to_unit: number @@ -14,2722 +14,2722 @@ export type CurrencyCode = Uppercase export const currencies = { aed: { priority: 100, - iso_code: 'AED', - name: 'United Arab Emirates Dirham', - symbol: 'د.إ', - alternate_symbols: ['DH', 'Dhs'], - subunit: 'Fils', + iso_code: "AED", + name: "United Arab Emirates Dirham", + symbol: "د.إ", + alternate_symbols: ["DH", "Dhs"], + subunit: "Fils", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '784', - smallest_denomination: 25 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "784", + smallest_denomination: 25, }, afn: { priority: 100, - iso_code: 'AFN', - name: 'Afghan Afghani', - symbol: '؋', - alternate_symbols: ['Af', 'Afs'], - subunit: 'Pul', + iso_code: "AFN", + name: "Afghan Afghani", + symbol: "؋", + alternate_symbols: ["Af", "Afs"], + subunit: "Pul", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '971', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "971", + smallest_denomination: 100, }, all: { priority: 100, - iso_code: 'ALL', - name: 'Albanian Lek', - symbol: 'L', - disambiguate_symbol: 'Lek', - alternate_symbols: ['Lek'], - subunit: 'Qintar', + iso_code: "ALL", + name: "Albanian Lek", + symbol: "L", + disambiguate_symbol: "Lek", + alternate_symbols: ["Lek"], + subunit: "Qintar", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '008', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "008", + smallest_denomination: 100, }, amd: { priority: 100, - iso_code: 'AMD', - name: 'Armenian Dram', - symbol: 'դր.', - alternate_symbols: ['dram'], - subunit: 'Luma', + iso_code: "AMD", + name: "Armenian Dram", + symbol: "դր.", + alternate_symbols: ["dram"], + subunit: "Luma", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '051', - smallest_denomination: 10 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "051", + smallest_denomination: 10, }, ang: { priority: 100, - iso_code: 'ANG', - name: 'Netherlands Antillean Gulden', - symbol: 'ƒ', - alternate_symbols: ['NAƒ', 'NAf', 'f'], - subunit: 'Cent', + iso_code: "ANG", + name: "Netherlands Antillean Gulden", + symbol: "ƒ", + alternate_symbols: ["NAƒ", "NAf", "f"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: 'ƒ', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '532', - smallest_denomination: 1 + html_entity: "ƒ", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "532", + smallest_denomination: 1, }, aoa: { priority: 100, - iso_code: 'AOA', - name: 'Angolan Kwanza', - symbol: 'Kz', + iso_code: "AOA", + name: "Angolan Kwanza", + symbol: "Kz", alternate_symbols: [], - subunit: 'Cêntimo', + subunit: "Cêntimo", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '973', - smallest_denomination: 10 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "973", + smallest_denomination: 10, }, ars: { priority: 100, - iso_code: 'ARS', - name: 'Argentine Peso', - symbol: '$', - disambiguate_symbol: '$m/n', - alternate_symbols: ['$m/n', 'm$n'], - subunit: 'Centavo', + iso_code: "ARS", + name: "Argentine Peso", + symbol: "$", + disambiguate_symbol: "$m/n", + alternate_symbols: ["$m/n", "m$n"], + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '032', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "032", + smallest_denomination: 1, }, aud: { priority: 4, - iso_code: 'AUD', - name: 'Australian Dollar', - symbol: '$', - disambiguate_symbol: 'A$', - alternate_symbols: ['A$'], - subunit: 'Cent', + iso_code: "AUD", + name: "Australian Dollar", + symbol: "$", + disambiguate_symbol: "A$", + alternate_symbols: ["A$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '036', - smallest_denomination: 5 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "036", + smallest_denomination: 5, }, awg: { priority: 100, - iso_code: 'AWG', - name: 'Aruban Florin', - symbol: 'ƒ', - alternate_symbols: ['Afl'], - subunit: 'Cent', + iso_code: "AWG", + name: "Aruban Florin", + symbol: "ƒ", + alternate_symbols: ["Afl"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: 'ƒ', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '533', - smallest_denomination: 5 + format: "%n %u", + html_entity: "ƒ", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "533", + smallest_denomination: 5, }, azn: { priority: 100, - iso_code: 'AZN', - name: 'Azerbaijani Manat', - symbol: '₼', - alternate_symbols: ['m', 'man'], - subunit: 'Qəpik', + iso_code: "AZN", + name: "Azerbaijani Manat", + symbol: "₼", + alternate_symbols: ["m", "man"], + subunit: "Qəpik", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '944', - smallest_denomination: 1 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "944", + smallest_denomination: 1, }, bam: { priority: 100, - iso_code: 'BAM', - name: 'Bosnia and Herzegovina Convertible Mark', - symbol: 'КМ', - alternate_symbols: ['KM'], - subunit: 'Fening', + iso_code: "BAM", + name: "Bosnia and Herzegovina Convertible Mark", + symbol: "КМ", + alternate_symbols: ["KM"], + subunit: "Fening", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '977', - smallest_denomination: 5 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "977", + smallest_denomination: 5, }, bbd: { priority: 100, - iso_code: 'BBD', - name: 'Barbadian Dollar', - symbol: '$', - disambiguate_symbol: 'Bds$', - alternate_symbols: ['Bds$'], - subunit: 'Cent', + iso_code: "BBD", + name: "Barbadian Dollar", + symbol: "$", + disambiguate_symbol: "Bds$", + alternate_symbols: ["Bds$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '052', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "052", + smallest_denomination: 1, }, bdt: { priority: 100, - iso_code: 'BDT', - name: 'Bangladeshi Taka', - symbol: '৳', - alternate_symbols: ['Tk'], - subunit: 'Paisa', + iso_code: "BDT", + name: "Bangladeshi Taka", + symbol: "৳", + alternate_symbols: ["Tk"], + subunit: "Paisa", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '050', - smallest_denomination: 1 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "050", + smallest_denomination: 1, }, bgn: { priority: 100, - iso_code: 'BGN', - name: 'Bulgarian Lev', - symbol: 'лв.', - alternate_symbols: ['lev', 'leva', 'лев', 'лева'], - subunit: 'Stotinka', + iso_code: "BGN", + name: "Bulgarian Lev", + symbol: "лв.", + alternate_symbols: ["lev", "leva", "лев", "лева"], + subunit: "Stotinka", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '975', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "975", + smallest_denomination: 1, }, bhd: { priority: 100, - iso_code: 'BHD', - name: 'Bahraini Dinar', - symbol: 'د.ب', - alternate_symbols: ['BD'], - subunit: 'Fils', + iso_code: "BHD", + name: "Bahraini Dinar", + symbol: "د.ب", + alternate_symbols: ["BD"], + subunit: "Fils", subunit_to_unit: 1000, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '048', - smallest_denomination: 5 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "048", + smallest_denomination: 5, }, bif: { priority: 100, - iso_code: 'BIF', - name: 'Burundian Franc', - symbol: 'Fr', - disambiguate_symbol: 'FBu', - alternate_symbols: ['FBu'], - subunit: 'Centime', + iso_code: "BIF", + name: "Burundian Franc", + symbol: "Fr", + disambiguate_symbol: "FBu", + alternate_symbols: ["FBu"], + subunit: "Centime", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '108', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "108", + smallest_denomination: 100, }, bmd: { priority: 100, - iso_code: 'BMD', - name: 'Bermudian Dollar', - symbol: '$', - disambiguate_symbol: 'BD$', - alternate_symbols: ['BD$'], - subunit: 'Cent', + iso_code: "BMD", + name: "Bermudian Dollar", + symbol: "$", + disambiguate_symbol: "BD$", + alternate_symbols: ["BD$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '060', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "060", + smallest_denomination: 1, }, bnd: { priority: 100, - iso_code: 'BND', - name: 'Brunei Dollar', - symbol: '$', - disambiguate_symbol: 'BND', - alternate_symbols: ['B$'], - subunit: 'Sen', + iso_code: "BND", + name: "Brunei Dollar", + symbol: "$", + disambiguate_symbol: "BND", + alternate_symbols: ["B$"], + subunit: "Sen", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '096', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "096", + smallest_denomination: 1, }, bob: { priority: 100, - iso_code: 'BOB', - name: 'Bolivian Boliviano', - symbol: 'Bs.', - alternate_symbols: ['Bs'], - subunit: 'Centavo', + iso_code: "BOB", + name: "Bolivian Boliviano", + symbol: "Bs.", + alternate_symbols: ["Bs"], + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '068', - smallest_denomination: 10 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "068", + smallest_denomination: 10, }, brl: { priority: 100, - iso_code: 'BRL', - name: 'Brazilian Real', - symbol: 'R$', - subunit: 'Centavo', + iso_code: "BRL", + name: "Brazilian Real", + symbol: "R$", + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: 'R$', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '986', - smallest_denomination: 5 + html_entity: "R$", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "986", + smallest_denomination: 5, }, bsd: { priority: 100, - iso_code: 'BSD', - name: 'Bahamian Dollar', - symbol: '$', - disambiguate_symbol: 'BSD', - alternate_symbols: ['B$'], - subunit: 'Cent', + iso_code: "BSD", + name: "Bahamian Dollar", + symbol: "$", + disambiguate_symbol: "BSD", + alternate_symbols: ["B$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '044', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "044", + smallest_denomination: 1, }, btn: { priority: 100, - iso_code: 'BTN', - name: 'Bhutanese Ngultrum', - symbol: 'Nu.', - alternate_symbols: ['Nu'], - subunit: 'Chertrum', + iso_code: "BTN", + name: "Bhutanese Ngultrum", + symbol: "Nu.", + alternate_symbols: ["Nu"], + subunit: "Chertrum", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '064', - smallest_denomination: 5 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "064", + smallest_denomination: 5, }, bwp: { priority: 100, - iso_code: 'BWP', - name: 'Botswana Pula', - symbol: 'P', + iso_code: "BWP", + name: "Botswana Pula", + symbol: "P", alternate_symbols: [], - subunit: 'Thebe', + subunit: "Thebe", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '072', - smallest_denomination: 5 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "072", + smallest_denomination: 5, }, byn: { priority: 100, - iso_code: 'BYN', - name: 'Belarusian Ruble', - symbol: 'Br', - disambiguate_symbol: 'BYN', - alternate_symbols: ['бел. руб.', 'б.р.', 'руб.', 'р.'], - subunit: 'Kapeyka', + iso_code: "BYN", + name: "Belarusian Ruble", + symbol: "Br", + disambiguate_symbol: "BYN", + alternate_symbols: ["бел. руб.", "б.р.", "руб.", "р."], + subunit: "Kapeyka", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: ',', - thousands_separator: ' ', - iso_numeric: '933', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ",", + thousands_separator: " ", + iso_numeric: "933", + smallest_denomination: 1, }, byr: { priority: 50, - iso_code: 'BYR', - name: 'Belarusian Ruble', - symbol: 'Br', - disambiguate_symbol: 'BYR', - alternate_symbols: ['бел. руб.', 'б.р.', 'руб.', 'р.'], + iso_code: "BYR", + name: "Belarusian Ruble", + symbol: "Br", + disambiguate_symbol: "BYR", + alternate_symbols: ["бел. руб.", "б.р.", "руб.", "р."], subunit: null, subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: ',', - thousands_separator: ' ', - iso_numeric: '974', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ",", + thousands_separator: " ", + iso_numeric: "974", + smallest_denomination: 100, }, bzd: { priority: 100, - iso_code: 'BZD', - name: 'Belize Dollar', - symbol: '$', - disambiguate_symbol: 'BZ$', - alternate_symbols: ['BZ$'], - subunit: 'Cent', + iso_code: "BZD", + name: "Belize Dollar", + symbol: "$", + disambiguate_symbol: "BZ$", + alternate_symbols: ["BZ$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '084', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "084", + smallest_denomination: 1, }, cad: { priority: 5, - iso_code: 'CAD', - name: 'Canadian Dollar', - symbol: '$', - disambiguate_symbol: 'C$', - alternate_symbols: ['C$', 'CAD$'], - subunit: 'Cent', + iso_code: "CAD", + name: "Canadian Dollar", + symbol: "$", + disambiguate_symbol: "C$", + alternate_symbols: ["C$", "CAD$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '124', - smallest_denomination: 5 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "124", + smallest_denomination: 5, }, cdf: { priority: 100, - iso_code: 'CDF', - name: 'Congolese Franc', - symbol: 'Fr', - disambiguate_symbol: 'FC', - alternate_symbols: ['FC'], - subunit: 'Centime', + iso_code: "CDF", + name: "Congolese Franc", + symbol: "Fr", + disambiguate_symbol: "FC", + alternate_symbols: ["FC"], + subunit: "Centime", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '976', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "976", + smallest_denomination: 1, }, chf: { priority: 100, - iso_code: 'CHF', - name: 'Swiss Franc', - symbol: 'CHF', - alternate_symbols: ['SFr', 'Fr'], - subunit: 'Rappen', + iso_code: "CHF", + name: "Swiss Franc", + symbol: "CHF", + alternate_symbols: ["SFr", "Fr"], + subunit: "Rappen", subunit_to_unit: 100, symbol_first: true, - format: '%u%n', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '756', - smallest_denomination: 5 + format: "%u%n", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "756", + smallest_denomination: 5, }, clf: { priority: 100, - iso_code: 'CLF', - name: 'Unidad de Fomento', - symbol: 'UF', + iso_code: "CLF", + name: "Unidad de Fomento", + symbol: "UF", alternate_symbols: [], - subunit: 'Peso', + subunit: "Peso", subunit_to_unit: 10000, symbol_first: true, - html_entity: '₱', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '990' + html_entity: "₱", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "990", }, clp: { priority: 100, - iso_code: 'CLP', - name: 'Chilean Peso', - symbol: '$', - disambiguate_symbol: 'CLP', + iso_code: "CLP", + name: "Chilean Peso", + symbol: "$", + disambiguate_symbol: "CLP", alternate_symbols: [], - subunit: 'Peso', + subunit: "Peso", subunit_to_unit: 1, symbol_first: true, - html_entity: '$', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '152', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "152", + smallest_denomination: 1, }, cny: { priority: 100, - iso_code: 'CNY', - name: 'Chinese Renminbi Yuan', - symbol: '¥', - alternate_symbols: ['CN¥', '元', 'CN元'], - subunit: 'Fen', + iso_code: "CNY", + name: "Chinese Renminbi Yuan", + symbol: "¥", + alternate_symbols: ["CN¥", "元", "CN元"], + subunit: "Fen", subunit_to_unit: 100, symbol_first: true, - html_entity: '¥', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '156', - smallest_denomination: 1 + html_entity: "¥", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "156", + smallest_denomination: 1, }, cop: { priority: 100, - iso_code: 'COP', - name: 'Colombian Peso', - symbol: '$', - disambiguate_symbol: 'COL$', - alternate_symbols: ['COL$'], - subunit: 'Centavo', + iso_code: "COP", + name: "Colombian Peso", + symbol: "$", + disambiguate_symbol: "COL$", + alternate_symbols: ["COL$"], + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '170', - smallest_denomination: 20 + html_entity: "$", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "170", + smallest_denomination: 20, }, crc: { priority: 100, - iso_code: 'CRC', - name: 'Costa Rican Colón', - symbol: '₡', - alternate_symbols: ['¢'], - subunit: 'Céntimo', + iso_code: "CRC", + name: "Costa Rican Colón", + symbol: "₡", + alternate_symbols: ["¢"], + subunit: "Céntimo", subunit_to_unit: 100, symbol_first: true, - html_entity: '₡', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '188', - smallest_denomination: 500 + html_entity: "₡", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "188", + smallest_denomination: 500, }, cuc: { priority: 100, - iso_code: 'CUC', - name: 'Cuban Convertible Peso', - symbol: '$', - disambiguate_symbol: 'CUC$', - alternate_symbols: ['CUC$'], - subunit: 'Centavo', + iso_code: "CUC", + name: "Cuban Convertible Peso", + symbol: "$", + disambiguate_symbol: "CUC$", + alternate_symbols: ["CUC$"], + subunit: "Centavo", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '931', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "931", + smallest_denomination: 1, }, cup: { priority: 100, - iso_code: 'CUP', - name: 'Cuban Peso', - symbol: '$', - disambiguate_symbol: '$MN', - alternate_symbols: ['$MN'], - subunit: 'Centavo', + iso_code: "CUP", + name: "Cuban Peso", + symbol: "$", + disambiguate_symbol: "$MN", + alternate_symbols: ["$MN"], + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '₱', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '192', - smallest_denomination: 1 + html_entity: "₱", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "192", + smallest_denomination: 1, }, cve: { priority: 100, - iso_code: 'CVE', - name: 'Cape Verdean Escudo', - symbol: '$', - disambiguate_symbol: 'Esc', - alternate_symbols: ['Esc'], - subunit: 'Centavo', + iso_code: "CVE", + name: "Cape Verdean Escudo", + symbol: "$", + disambiguate_symbol: "Esc", + alternate_symbols: ["Esc"], + subunit: "Centavo", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '132', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "132", + smallest_denomination: 100, }, czk: { priority: 100, - iso_code: 'CZK', - name: 'Czech Koruna', - symbol: 'Kč', + iso_code: "CZK", + name: "Czech Koruna", + symbol: "Kč", alternate_symbols: [], - subunit: 'Haléř', + subunit: "Haléř", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: ',', - thousands_separator: ' ', - iso_numeric: '203', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ",", + thousands_separator: " ", + iso_numeric: "203", + smallest_denomination: 100, }, djf: { priority: 100, - iso_code: 'DJF', - name: 'Djiboutian Franc', - symbol: 'Fdj', + iso_code: "DJF", + name: "Djiboutian Franc", + symbol: "Fdj", alternate_symbols: [], - subunit: 'Centime', + subunit: "Centime", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '262', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "262", + smallest_denomination: 100, }, dkk: { priority: 100, - iso_code: 'DKK', - name: 'Danish Krone', - symbol: 'kr.', - disambiguate_symbol: 'DKK', - alternate_symbols: [',-'], - subunit: 'Øre', + iso_code: "DKK", + name: "Danish Krone", + symbol: "kr.", + disambiguate_symbol: "DKK", + alternate_symbols: [",-"], + subunit: "Øre", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '208', - smallest_denomination: 50 + format: "%n %u", + html_entity: "", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "208", + smallest_denomination: 50, }, dop: { priority: 100, - iso_code: 'DOP', - name: 'Dominican Peso', - symbol: '$', - disambiguate_symbol: 'RD$', - alternate_symbols: ['RD$'], - subunit: 'Centavo', + iso_code: "DOP", + name: "Dominican Peso", + symbol: "$", + disambiguate_symbol: "RD$", + alternate_symbols: ["RD$"], + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '₱', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '214', - smallest_denomination: 100 + html_entity: "₱", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "214", + smallest_denomination: 100, }, dzd: { priority: 100, - iso_code: 'DZD', - name: 'Algerian Dinar', - symbol: 'د.ج', - alternate_symbols: ['DA'], - subunit: 'Centime', + iso_code: "DZD", + name: "Algerian Dinar", + symbol: "د.ج", + alternate_symbols: ["DA"], + subunit: "Centime", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '012', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "012", + smallest_denomination: 100, }, egp: { priority: 100, - iso_code: 'EGP', - name: 'Egyptian Pound', - symbol: 'ج.م', - alternate_symbols: ['LE', 'E£', 'L.E.'], - subunit: 'Piastre', + iso_code: "EGP", + name: "Egyptian Pound", + symbol: "ج.م", + alternate_symbols: ["LE", "E£", "L.E."], + subunit: "Piastre", subunit_to_unit: 100, symbol_first: true, - html_entity: '£', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '818', - smallest_denomination: 25 + html_entity: "£", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "818", + smallest_denomination: 25, }, ern: { priority: 100, - iso_code: 'ERN', - name: 'Eritrean Nakfa', - symbol: 'Nfk', + iso_code: "ERN", + name: "Eritrean Nakfa", + symbol: "Nfk", alternate_symbols: [], - subunit: 'Cent', + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '232', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "232", + smallest_denomination: 1, }, etb: { priority: 100, - iso_code: 'ETB', - name: 'Ethiopian Birr', - symbol: 'Br', - disambiguate_symbol: 'ETB', + iso_code: "ETB", + name: "Ethiopian Birr", + symbol: "Br", + disambiguate_symbol: "ETB", alternate_symbols: [], - subunit: 'Santim', + subunit: "Santim", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '230', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "230", + smallest_denomination: 1, }, eur: { priority: 2, - iso_code: 'EUR', - name: 'Euro', - symbol: '€', + iso_code: "EUR", + name: "Euro", + symbol: "€", alternate_symbols: [], - subunit: 'Cent', + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '€', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '978', - smallest_denomination: 1 + html_entity: "€", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "978", + smallest_denomination: 1, }, fjd: { priority: 100, - iso_code: 'FJD', - name: 'Fijian Dollar', - symbol: '$', - disambiguate_symbol: 'FJ$', - alternate_symbols: ['FJ$'], - subunit: 'Cent', + iso_code: "FJD", + name: "Fijian Dollar", + symbol: "$", + disambiguate_symbol: "FJ$", + alternate_symbols: ["FJ$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '242', - smallest_denomination: 5 + format: "%n %u", + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "242", + smallest_denomination: 5, }, fkp: { priority: 100, - iso_code: 'FKP', - name: 'Falkland Pound', - symbol: '£', - disambiguate_symbol: 'FK£', - alternate_symbols: ['FK£'], - subunit: 'Penny', + iso_code: "FKP", + name: "Falkland Pound", + symbol: "£", + disambiguate_symbol: "FK£", + alternate_symbols: ["FK£"], + subunit: "Penny", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '£', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '238', - smallest_denomination: 1 + format: "%n %u", + html_entity: "£", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "238", + smallest_denomination: 1, }, gbp: { priority: 3, - iso_code: 'GBP', - name: 'British Pound', - symbol: '£', + iso_code: "GBP", + name: "British Pound", + symbol: "£", alternate_symbols: [], - subunit: 'Penny', + subunit: "Penny", subunit_to_unit: 100, symbol_first: true, - html_entity: '£', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '826', - smallest_denomination: 1 + html_entity: "£", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "826", + smallest_denomination: 1, }, gel: { priority: 100, - iso_code: 'GEL', - name: 'Georgian Lari', - symbol: '₾', - alternate_symbols: ['lari'], - subunit: 'Tetri', + iso_code: "GEL", + name: "Georgian Lari", + symbol: "₾", + alternate_symbols: ["lari"], + subunit: "Tetri", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '981', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "981", + smallest_denomination: 1, }, ghs: { priority: 100, - iso_code: 'GHS', - name: 'Ghanaian Cedi', - symbol: '₵', - alternate_symbols: ['GH¢', 'GH₵'], - subunit: 'Pesewa', + iso_code: "GHS", + name: "Ghanaian Cedi", + symbol: "₵", + alternate_symbols: ["GH¢", "GH₵"], + subunit: "Pesewa", subunit_to_unit: 100, symbol_first: true, - html_entity: '₵', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '936', - smallest_denomination: 1 + html_entity: "₵", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "936", + smallest_denomination: 1, }, gip: { priority: 100, - iso_code: 'GIP', - name: 'Gibraltar Pound', - symbol: '£', - disambiguate_symbol: 'GIP', + iso_code: "GIP", + name: "Gibraltar Pound", + symbol: "£", + disambiguate_symbol: "GIP", alternate_symbols: [], - subunit: 'Penny', + subunit: "Penny", subunit_to_unit: 100, symbol_first: true, - html_entity: '£', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '292', - smallest_denomination: 1 + html_entity: "£", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "292", + smallest_denomination: 1, }, gmd: { priority: 100, - iso_code: 'GMD', - name: 'Gambian Dalasi', - symbol: 'D', + iso_code: "GMD", + name: "Gambian Dalasi", + symbol: "D", alternate_symbols: [], - subunit: 'Butut', + subunit: "Butut", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '270', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "270", + smallest_denomination: 1, }, gnf: { priority: 100, - iso_code: 'GNF', - name: 'Guinean Franc', - symbol: 'Fr', - disambiguate_symbol: 'FG', - alternate_symbols: ['FG', 'GFr'], - subunit: 'Centime', + iso_code: "GNF", + name: "Guinean Franc", + symbol: "Fr", + disambiguate_symbol: "FG", + alternate_symbols: ["FG", "GFr"], + subunit: "Centime", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '324', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "324", + smallest_denomination: 100, }, gtq: { priority: 100, - iso_code: 'GTQ', - name: 'Guatemalan Quetzal', - symbol: 'Q', + iso_code: "GTQ", + name: "Guatemalan Quetzal", + symbol: "Q", alternate_symbols: [], - subunit: 'Centavo', + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '320', - smallest_denomination: 1 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "320", + smallest_denomination: 1, }, gyd: { priority: 100, - iso_code: 'GYD', - name: 'Guyanese Dollar', - symbol: '$', - disambiguate_symbol: 'G$', - alternate_symbols: ['G$'], - subunit: 'Cent', + iso_code: "GYD", + name: "Guyanese Dollar", + symbol: "$", + disambiguate_symbol: "G$", + alternate_symbols: ["G$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '328', - smallest_denomination: 100 + format: "%n %u", + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "328", + smallest_denomination: 100, }, hkd: { priority: 100, - iso_code: 'HKD', - name: 'Hong Kong Dollar', - symbol: '$', - disambiguate_symbol: 'HK$', - alternate_symbols: ['HK$'], - subunit: 'Cent', + iso_code: "HKD", + name: "Hong Kong Dollar", + symbol: "$", + disambiguate_symbol: "HK$", + alternate_symbols: ["HK$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '344', - smallest_denomination: 10 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "344", + smallest_denomination: 10, }, hnl: { priority: 100, - iso_code: 'HNL', - name: 'Honduran Lempira', - symbol: 'L', - disambiguate_symbol: 'HNL', + iso_code: "HNL", + name: "Honduran Lempira", + symbol: "L", + disambiguate_symbol: "HNL", alternate_symbols: [], - subunit: 'Centavo', + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '340', - smallest_denomination: 5 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "340", + smallest_denomination: 5, }, htg: { priority: 100, - iso_code: 'HTG', - name: 'Haitian Gourde', - symbol: 'G', + iso_code: "HTG", + name: "Haitian Gourde", + symbol: "G", alternate_symbols: [], - subunit: 'Centime', + subunit: "Centime", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '332', - smallest_denomination: 5 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "332", + smallest_denomination: 5, }, huf: { priority: 100, - iso_code: 'HUF', - name: 'Hungarian Forint', - symbol: 'Ft', + iso_code: "HUF", + name: "Hungarian Forint", + symbol: "Ft", alternate_symbols: [], - subunit: '', + subunit: "", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: ',', - thousands_separator: ' ', - iso_numeric: '348', - smallest_denomination: 5 + format: "%n %u", + html_entity: "", + decimal_mark: ",", + thousands_separator: " ", + iso_numeric: "348", + smallest_denomination: 5, }, idr: { priority: 100, - iso_code: 'IDR', - name: 'Indonesian Rupiah', - symbol: 'Rp', + iso_code: "IDR", + name: "Indonesian Rupiah", + symbol: "Rp", alternate_symbols: [], - subunit: 'Sen', + subunit: "Sen", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '360', - smallest_denomination: 5000 + html_entity: "", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "360", + smallest_denomination: 5000, }, ils: { priority: 100, - iso_code: 'ILS', - name: 'Israeli New Sheqel', - symbol: '₪', - alternate_symbols: ['ש״ח', 'NIS'], - subunit: 'Agora', + iso_code: "ILS", + name: "Israeli New Sheqel", + symbol: "₪", + alternate_symbols: ["ש״ח", "NIS"], + subunit: "Agora", subunit_to_unit: 100, symbol_first: true, - html_entity: '₪', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '376', - smallest_denomination: 10 + html_entity: "₪", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "376", + smallest_denomination: 10, }, inr: { priority: 100, - iso_code: 'INR', - name: 'Indian Rupee', - symbol: '₹', - alternate_symbols: ['Rs', '৳', '૱', '௹', 'रु', '₨'], - subunit: 'Paisa', + iso_code: "INR", + name: "Indian Rupee", + symbol: "₹", + alternate_symbols: ["Rs", "৳", "૱", "௹", "रु", "₨"], + subunit: "Paisa", subunit_to_unit: 100, symbol_first: true, - html_entity: '₹', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '356', - smallest_denomination: 50 + html_entity: "₹", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "356", + smallest_denomination: 50, }, iqd: { priority: 100, - iso_code: 'IQD', - name: 'Iraqi Dinar', - symbol: 'ع.د', + iso_code: "IQD", + name: "Iraqi Dinar", + symbol: "ع.د", alternate_symbols: [], - subunit: 'Fils', + subunit: "Fils", subunit_to_unit: 1000, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '368', - smallest_denomination: 50000 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "368", + smallest_denomination: 50000, }, irr: { priority: 100, - iso_code: 'IRR', - name: 'Iranian Rial', - symbol: '﷼', + iso_code: "IRR", + name: "Iranian Rial", + symbol: "﷼", alternate_symbols: [], subunit: null, subunit_to_unit: 100, symbol_first: true, - html_entity: '﷼', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '364', - smallest_denomination: 5000 + html_entity: "﷼", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "364", + smallest_denomination: 5000, }, isk: { priority: 100, - iso_code: 'ISK', - name: 'Icelandic Króna', - symbol: 'kr.', - alternate_symbols: ['Íkr'], + iso_code: "ISK", + name: "Icelandic Króna", + symbol: "kr.", + alternate_symbols: ["Íkr"], subunit: null, subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '352', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "352", + smallest_denomination: 1, }, jmd: { priority: 100, - iso_code: 'JMD', - name: 'Jamaican Dollar', - symbol: '$', - disambiguate_symbol: 'J$', - alternate_symbols: ['J$'], - subunit: 'Cent', + iso_code: "JMD", + name: "Jamaican Dollar", + symbol: "$", + disambiguate_symbol: "J$", + alternate_symbols: ["J$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '388', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "388", + smallest_denomination: 1, }, jod: { priority: 100, - iso_code: 'JOD', - name: 'Jordanian Dinar', - symbol: 'د.ا', - alternate_symbols: ['JD'], - subunit: 'Fils', + iso_code: "JOD", + name: "Jordanian Dinar", + symbol: "د.ا", + alternate_symbols: ["JD"], + subunit: "Fils", subunit_to_unit: 1000, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '400', - smallest_denomination: 5 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "400", + smallest_denomination: 5, }, jpy: { priority: 6, - iso_code: 'JPY', - name: 'Japanese Yen', - symbol: '¥', - alternate_symbols: ['円', '圓'], + iso_code: "JPY", + name: "Japanese Yen", + symbol: "¥", + alternate_symbols: ["円", "圓"], subunit: null, subunit_to_unit: 1, symbol_first: true, - html_entity: '¥', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '392', - smallest_denomination: 1 + html_entity: "¥", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "392", + smallest_denomination: 1, }, kes: { priority: 100, - iso_code: 'KES', - name: 'Kenyan Shilling', - symbol: 'KSh', - alternate_symbols: ['Sh'], - subunit: 'Cent', + iso_code: "KES", + name: "Kenyan Shilling", + symbol: "KSh", + alternate_symbols: ["Sh"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '404', - smallest_denomination: 50 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "404", + smallest_denomination: 50, }, kgs: { priority: 100, - iso_code: 'KGS', - name: 'Kyrgyzstani Som', - symbol: 'som', - alternate_symbols: ['сом'], - subunit: 'Tyiyn', + iso_code: "KGS", + name: "Kyrgyzstani Som", + symbol: "som", + alternate_symbols: ["сом"], + subunit: "Tyiyn", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '417', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "417", + smallest_denomination: 1, }, khr: { priority: 100, - iso_code: 'KHR', - name: 'Cambodian Riel', - symbol: '៛', + iso_code: "KHR", + name: "Cambodian Riel", + symbol: "៛", alternate_symbols: [], - subunit: 'Sen', + subunit: "Sen", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '៛', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '116', - smallest_denomination: 5000 + format: "%n %u", + html_entity: "៛", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "116", + smallest_denomination: 5000, }, kmf: { priority: 100, - iso_code: 'KMF', - name: 'Comorian Franc', - symbol: 'Fr', - disambiguate_symbol: 'CF', - alternate_symbols: ['CF'], - subunit: 'Centime', + iso_code: "KMF", + name: "Comorian Franc", + symbol: "Fr", + disambiguate_symbol: "CF", + alternate_symbols: ["CF"], + subunit: "Centime", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '174', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "174", + smallest_denomination: 100, }, kpw: { priority: 100, - iso_code: 'KPW', - name: 'North Korean Won', - symbol: '₩', + iso_code: "KPW", + name: "North Korean Won", + symbol: "₩", alternate_symbols: [], - subunit: 'Chŏn', + subunit: "Chŏn", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '₩', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '408', - smallest_denomination: 1 + format: "%n %u", + html_entity: "₩", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "408", + smallest_denomination: 1, }, krw: { priority: 100, - iso_code: 'KRW', - name: 'South Korean Won', - symbol: '₩', + iso_code: "KRW", + name: "South Korean Won", + symbol: "₩", subunit: null, subunit_to_unit: 1, alternate_symbols: [], symbol_first: true, - html_entity: '₩', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '410', - smallest_denomination: 1 + html_entity: "₩", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "410", + smallest_denomination: 1, }, kwd: { priority: 100, - iso_code: 'KWD', - name: 'Kuwaiti Dinar', - symbol: 'د.ك', - alternate_symbols: ['K.D.'], - subunit: 'Fils', + iso_code: "KWD", + name: "Kuwaiti Dinar", + symbol: "د.ك", + alternate_symbols: ["K.D."], + subunit: "Fils", subunit_to_unit: 1000, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '414', - smallest_denomination: 5 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "414", + smallest_denomination: 5, }, kyd: { priority: 100, - iso_code: 'KYD', - name: 'Cayman Islands Dollar', - symbol: '$', - disambiguate_symbol: 'CI$', - alternate_symbols: ['CI$'], - subunit: 'Cent', + iso_code: "KYD", + name: "Cayman Islands Dollar", + symbol: "$", + disambiguate_symbol: "CI$", + alternate_symbols: ["CI$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '136', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "136", + smallest_denomination: 1, }, kzt: { priority: 100, - iso_code: 'KZT', - name: 'Kazakhstani Tenge', - symbol: '₸', + iso_code: "KZT", + name: "Kazakhstani Tenge", + symbol: "₸", alternate_symbols: [], - subunit: 'Tiyn', + subunit: "Tiyn", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '398', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "398", + smallest_denomination: 100, }, lak: { priority: 100, - iso_code: 'LAK', - name: 'Lao Kip', - symbol: '₭', - alternate_symbols: ['₭N'], - subunit: 'Att', + iso_code: "LAK", + name: "Lao Kip", + symbol: "₭", + alternate_symbols: ["₭N"], + subunit: "Att", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '₭', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '418', - smallest_denomination: 10 + format: "%n %u", + html_entity: "₭", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "418", + smallest_denomination: 10, }, lbp: { priority: 100, - iso_code: 'LBP', - name: 'Lebanese Pound', - symbol: 'ل.ل', - alternate_symbols: ['£', 'L£'], - subunit: 'Piastre', + iso_code: "LBP", + name: "Lebanese Pound", + symbol: "ل.ل", + alternate_symbols: ["£", "L£"], + subunit: "Piastre", subunit_to_unit: 100, symbol_first: true, - html_entity: '£', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '422', - smallest_denomination: 25000 + html_entity: "£", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "422", + smallest_denomination: 25000, }, lkr: { priority: 100, - iso_code: 'LKR', - name: 'Sri Lankan Rupee', - symbol: '₨', - disambiguate_symbol: 'SLRs', - alternate_symbols: ['රු', 'ரூ', 'SLRs', '/-'], - subunit: 'Cent', + iso_code: "LKR", + name: "Sri Lankan Rupee", + symbol: "₨", + disambiguate_symbol: "SLRs", + alternate_symbols: ["රු", "ரூ", "SLRs", "/-"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '₨', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '144', - smallest_denomination: 100 + format: "%n %u", + html_entity: "₨", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "144", + smallest_denomination: 100, }, lrd: { priority: 100, - iso_code: 'LRD', - name: 'Liberian Dollar', - symbol: '$', - disambiguate_symbol: 'L$', - alternate_symbols: ['L$'], - subunit: 'Cent', + iso_code: "LRD", + name: "Liberian Dollar", + symbol: "$", + disambiguate_symbol: "L$", + alternate_symbols: ["L$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '430', - smallest_denomination: 5 + format: "%n %u", + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "430", + smallest_denomination: 5, }, lsl: { priority: 100, - iso_code: 'LSL', - name: 'Lesotho Loti', - symbol: 'L', - disambiguate_symbol: 'M', - alternate_symbols: ['M'], - subunit: 'Sente', + iso_code: "LSL", + name: "Lesotho Loti", + symbol: "L", + disambiguate_symbol: "M", + alternate_symbols: ["M"], + subunit: "Sente", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '426', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "426", + smallest_denomination: 1, }, lyd: { priority: 100, - iso_code: 'LYD', - name: 'Libyan Dinar', - symbol: 'ل.د', - alternate_symbols: ['LD'], - subunit: 'Dirham', + iso_code: "LYD", + name: "Libyan Dinar", + symbol: "ل.د", + alternate_symbols: ["LD"], + subunit: "Dirham", subunit_to_unit: 1000, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '434', - smallest_denomination: 50 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "434", + smallest_denomination: 50, }, mad: { priority: 100, - iso_code: 'MAD', - name: 'Moroccan Dirham', - symbol: 'د.م.', + iso_code: "MAD", + name: "Moroccan Dirham", + symbol: "د.م.", alternate_symbols: [], - subunit: 'Centime', + subunit: "Centime", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '504', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "504", + smallest_denomination: 1, }, mdl: { priority: 100, - iso_code: 'MDL', - name: 'Moldovan Leu', - symbol: 'L', - alternate_symbols: ['lei'], - subunit: 'Ban', + iso_code: "MDL", + name: "Moldovan Leu", + symbol: "L", + alternate_symbols: ["lei"], + subunit: "Ban", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '498', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "498", + smallest_denomination: 1, }, mga: { priority: 100, - iso_code: 'MGA', - name: 'Malagasy Ariary', - symbol: 'Ar', + iso_code: "MGA", + name: "Malagasy Ariary", + symbol: "Ar", alternate_symbols: [], - subunit: 'Iraimbilanja', + subunit: "Iraimbilanja", subunit_to_unit: 5, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '969', - smallest_denomination: 1 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "969", + smallest_denomination: 1, }, mkd: { priority: 100, - iso_code: 'MKD', - name: 'Macedonian Denar', - symbol: 'ден', + iso_code: "MKD", + name: "Macedonian Denar", + symbol: "ден", alternate_symbols: [], - subunit: 'Deni', + subunit: "Deni", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '807', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "807", + smallest_denomination: 100, }, mmk: { priority: 100, - iso_code: 'MMK', - name: 'Myanmar Kyat', - symbol: 'K', - disambiguate_symbol: 'MMK', + iso_code: "MMK", + name: "Myanmar Kyat", + symbol: "K", + disambiguate_symbol: "MMK", alternate_symbols: [], - subunit: 'Pya', + subunit: "Pya", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '104', - smallest_denomination: 50 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "104", + smallest_denomination: 50, }, mnt: { priority: 100, - iso_code: 'MNT', - name: 'Mongolian Tögrög', - symbol: '₮', + iso_code: "MNT", + name: "Mongolian Tögrög", + symbol: "₮", alternate_symbols: [], - subunit: 'Möngö', + subunit: "Möngö", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '₮', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '496', - smallest_denomination: 2000 + format: "%n %u", + html_entity: "₮", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "496", + smallest_denomination: 2000, }, mop: { priority: 100, - iso_code: 'MOP', - name: 'Macanese Pataca', - symbol: 'P', - alternate_symbols: ['MOP$'], - subunit: 'Avo', + iso_code: "MOP", + name: "Macanese Pataca", + symbol: "P", + alternate_symbols: ["MOP$"], + subunit: "Avo", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '446', - smallest_denomination: 10 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "446", + smallest_denomination: 10, }, mru: { priority: 100, - iso_code: 'MRU', - name: 'Mauritanian Ouguiya', - symbol: 'UM', + iso_code: "MRU", + name: "Mauritanian Ouguiya", + symbol: "UM", alternate_symbols: [], - subunit: 'Khoums', + subunit: "Khoums", subunit_to_unit: 5, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '929', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "929", + smallest_denomination: 1, }, mur: { priority: 100, - iso_code: 'MUR', - name: 'Mauritian Rupee', - symbol: '₨', + iso_code: "MUR", + name: "Mauritian Rupee", + symbol: "₨", alternate_symbols: [], - subunit: 'Cent', + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '₨', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '480', - smallest_denomination: 100 + html_entity: "₨", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "480", + smallest_denomination: 100, }, mvr: { priority: 100, - iso_code: 'MVR', - name: 'Maldivian Rufiyaa', - symbol: 'MVR', - alternate_symbols: ['MRF', 'Rf', '/-', 'ރ'], - subunit: 'Laari', + iso_code: "MVR", + name: "Maldivian Rufiyaa", + symbol: "MVR", + alternate_symbols: ["MRF", "Rf", "/-", "ރ"], + subunit: "Laari", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '462', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "462", + smallest_denomination: 1, }, mwk: { priority: 100, - iso_code: 'MWK', - name: 'Malawian Kwacha', - symbol: 'MK', + iso_code: "MWK", + name: "Malawian Kwacha", + symbol: "MK", alternate_symbols: [], - subunit: 'Tambala', + subunit: "Tambala", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '454', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "454", + smallest_denomination: 1, }, mxn: { priority: 100, - iso_code: 'MXN', - name: 'Mexican Peso', - symbol: '$', - disambiguate_symbol: 'MEX$', - alternate_symbols: ['MEX$'], - subunit: 'Centavo', + iso_code: "MXN", + name: "Mexican Peso", + symbol: "$", + disambiguate_symbol: "MEX$", + alternate_symbols: ["MEX$"], + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '484', - smallest_denomination: 5 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "484", + smallest_denomination: 5, }, myr: { priority: 100, - iso_code: 'MYR', - name: 'Malaysian Ringgit', - symbol: 'RM', + iso_code: "MYR", + name: "Malaysian Ringgit", + symbol: "RM", alternate_symbols: [], - subunit: 'Sen', + subunit: "Sen", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '458', - smallest_denomination: 5 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "458", + smallest_denomination: 5, }, mzn: { priority: 100, - iso_code: 'MZN', - name: 'Mozambican Metical', - symbol: 'MTn', - alternate_symbols: ['MZN'], - subunit: 'Centavo', + iso_code: "MZN", + name: "Mozambican Metical", + symbol: "MTn", + alternate_symbols: ["MZN"], + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '943', - smallest_denomination: 1 + html_entity: "", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "943", + smallest_denomination: 1, }, nad: { priority: 100, - iso_code: 'NAD', - name: 'Namibian Dollar', - symbol: '$', - disambiguate_symbol: 'N$', - alternate_symbols: ['N$'], - subunit: 'Cent', + iso_code: "NAD", + name: "Namibian Dollar", + symbol: "$", + disambiguate_symbol: "N$", + alternate_symbols: ["N$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '516', - smallest_denomination: 5 + format: "%n %u", + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "516", + smallest_denomination: 5, }, ngn: { priority: 100, - iso_code: 'NGN', - name: 'Nigerian Naira', - symbol: '₦', + iso_code: "NGN", + name: "Nigerian Naira", + symbol: "₦", alternate_symbols: [], - subunit: 'Kobo', + subunit: "Kobo", subunit_to_unit: 100, symbol_first: true, - html_entity: '₦', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '566', - smallest_denomination: 50 + html_entity: "₦", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "566", + smallest_denomination: 50, }, nio: { priority: 100, - iso_code: 'NIO', - name: 'Nicaraguan Córdoba', - symbol: 'C$', - disambiguate_symbol: 'NIO$', + iso_code: "NIO", + name: "Nicaraguan Córdoba", + symbol: "C$", + disambiguate_symbol: "NIO$", alternate_symbols: [], - subunit: 'Centavo', + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '558', - smallest_denomination: 5 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "558", + smallest_denomination: 5, }, nok: { priority: 100, - iso_code: 'NOK', - name: 'Norwegian Krone', - symbol: 'kr', - disambiguate_symbol: 'NOK', - alternate_symbols: [',-'], - subunit: 'Øre', + iso_code: "NOK", + name: "Norwegian Krone", + symbol: "kr", + disambiguate_symbol: "NOK", + alternate_symbols: [",-"], + subunit: "Øre", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: 'kr', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '578', - smallest_denomination: 100 + format: "%n %u", + html_entity: "kr", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "578", + smallest_denomination: 100, }, npr: { priority: 100, - iso_code: 'NPR', - name: 'Nepalese Rupee', - symbol: 'Rs.', - disambiguate_symbol: 'NPR', - alternate_symbols: ['Rs', 'रू', '₨'], - subunit: 'Paisa', + iso_code: "NPR", + name: "Nepalese Rupee", + symbol: "Rs.", + disambiguate_symbol: "NPR", + alternate_symbols: ["Rs", "रू", "₨"], + subunit: "Paisa", subunit_to_unit: 100, symbol_first: true, - html_entity: '₨', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '524', - smallest_denomination: 1 + html_entity: "₨", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "524", + smallest_denomination: 1, }, nzd: { priority: 100, - iso_code: 'NZD', - name: 'New Zealand Dollar', - symbol: '$', - disambiguate_symbol: 'NZ$', - alternate_symbols: ['NZ$'], - subunit: 'Cent', + iso_code: "NZD", + name: "New Zealand Dollar", + symbol: "$", + disambiguate_symbol: "NZ$", + alternate_symbols: ["NZ$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '554', - smallest_denomination: 10 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "554", + smallest_denomination: 10, }, omr: { priority: 100, - iso_code: 'OMR', - name: 'Omani Rial', - symbol: 'ر.ع.', + iso_code: "OMR", + name: "Omani Rial", + symbol: "ر.ع.", alternate_symbols: [], - subunit: 'Baisa', + subunit: "Baisa", subunit_to_unit: 1000, symbol_first: true, - html_entity: '﷼', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '512', - smallest_denomination: 5 + html_entity: "﷼", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "512", + smallest_denomination: 5, }, pab: { priority: 100, - iso_code: 'PAB', - name: 'Panamanian Balboa', - symbol: 'B/.', + iso_code: "PAB", + name: "Panamanian Balboa", + symbol: "B/.", alternate_symbols: [], - subunit: 'Centésimo', + subunit: "Centésimo", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '590', - smallest_denomination: 1 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "590", + smallest_denomination: 1, }, pen: { priority: 100, - iso_code: 'PEN', - name: 'Peruvian Sol', - symbol: 'S/', + iso_code: "PEN", + name: "Peruvian Sol", + symbol: "S/", alternate_symbols: [], - subunit: 'Céntimo', + subunit: "Céntimo", subunit_to_unit: 100, symbol_first: true, - html_entity: 'S/', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '604', - smallest_denomination: 1 + html_entity: "S/", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "604", + smallest_denomination: 1, }, pgk: { priority: 100, - iso_code: 'PGK', - name: 'Papua New Guinean Kina', - symbol: 'K', - disambiguate_symbol: 'PGK', + iso_code: "PGK", + name: "Papua New Guinean Kina", + symbol: "K", + disambiguate_symbol: "PGK", alternate_symbols: [], - subunit: 'Toea', + subunit: "Toea", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '598', - smallest_denomination: 5 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "598", + smallest_denomination: 5, }, php: { priority: 100, - iso_code: 'PHP', - name: 'Philippine Peso', - symbol: '₱', - alternate_symbols: ['PHP', 'PhP', 'P'], - subunit: 'Centavo', + iso_code: "PHP", + name: "Philippine Peso", + symbol: "₱", + alternate_symbols: ["PHP", "PhP", "P"], + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '₱', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '608', - smallest_denomination: 1 + html_entity: "₱", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "608", + smallest_denomination: 1, }, pkr: { priority: 100, - iso_code: 'PKR', - name: 'Pakistani Rupee', - symbol: '₨', - disambiguate_symbol: 'PKR', - alternate_symbols: ['Rs'], - subunit: 'Paisa', + iso_code: "PKR", + name: "Pakistani Rupee", + symbol: "₨", + disambiguate_symbol: "PKR", + alternate_symbols: ["Rs"], + subunit: "Paisa", subunit_to_unit: 100, symbol_first: true, - html_entity: '₨', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '586', - smallest_denomination: 100 + html_entity: "₨", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "586", + smallest_denomination: 100, }, pln: { priority: 100, - iso_code: 'PLN', - name: 'Polish Złoty', - symbol: 'zł', + iso_code: "PLN", + name: "Polish Złoty", + symbol: "zł", alternate_symbols: [], - subunit: 'Grosz', + subunit: "Grosz", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: 'zł', - decimal_mark: ',', - thousands_separator: ' ', - iso_numeric: '985', - smallest_denomination: 1 + format: "%n %u", + html_entity: "zł", + decimal_mark: ",", + thousands_separator: " ", + iso_numeric: "985", + smallest_denomination: 1, }, pyg: { priority: 100, - iso_code: 'PYG', - name: 'Paraguayan Guaraní', - symbol: '₲', + iso_code: "PYG", + name: "Paraguayan Guaraní", + symbol: "₲", alternate_symbols: [], - subunit: 'Céntimo', + subunit: "Céntimo", subunit_to_unit: 1, symbol_first: true, - html_entity: '₲', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '600', - smallest_denomination: 5000 + html_entity: "₲", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "600", + smallest_denomination: 5000, }, qar: { priority: 100, - iso_code: 'QAR', - name: 'Qatari Riyal', - symbol: 'ر.ق', - alternate_symbols: ['QR'], - subunit: 'Dirham', + iso_code: "QAR", + name: "Qatari Riyal", + symbol: "ر.ق", + alternate_symbols: ["QR"], + subunit: "Dirham", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '﷼', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '634', - smallest_denomination: 1 + format: "%n %u", + html_entity: "﷼", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "634", + smallest_denomination: 1, }, ron: { priority: 100, - iso_code: 'RON', - name: 'Romanian Leu', - symbol: 'Lei', + iso_code: "RON", + name: "Romanian Leu", + symbol: "Lei", alternate_symbols: [], - subunit: 'Bani', + subunit: "Bani", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '946', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "946", + smallest_denomination: 1, }, rsd: { priority: 100, - iso_code: 'RSD', - name: 'Serbian Dinar', - symbol: 'РСД', - alternate_symbols: ['RSD', 'din', 'дин'], - subunit: 'Para', + iso_code: "RSD", + name: "Serbian Dinar", + symbol: "РСД", + alternate_symbols: ["RSD", "din", "дин"], + subunit: "Para", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '941', - smallest_denomination: 100 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "941", + smallest_denomination: 100, }, rub: { priority: 100, - iso_code: 'RUB', - name: 'Russian Ruble', - symbol: '₽', - alternate_symbols: ['руб.', 'р.'], - subunit: 'Kopeck', + iso_code: "RUB", + name: "Russian Ruble", + symbol: "₽", + alternate_symbols: ["руб.", "р."], + subunit: "Kopeck", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '₽', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '643', - smallest_denomination: 1 + format: "%n %u", + html_entity: "₽", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "643", + smallest_denomination: 1, }, rwf: { priority: 100, - iso_code: 'RWF', - name: 'Rwandan Franc', - symbol: 'FRw', - alternate_symbols: ['RF', 'R₣'], - subunit: 'Centime', + iso_code: "RWF", + name: "Rwandan Franc", + symbol: "FRw", + alternate_symbols: ["RF", "R₣"], + subunit: "Centime", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '646', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "646", + smallest_denomination: 100, }, sar: { priority: 100, - iso_code: 'SAR', - name: 'Saudi Riyal', - symbol: 'ر.س', - alternate_symbols: ['SR', '﷼'], - subunit: 'Hallallah', + iso_code: "SAR", + name: "Saudi Riyal", + symbol: "ر.س", + alternate_symbols: ["SR", "﷼"], + subunit: "Hallallah", subunit_to_unit: 100, symbol_first: true, - html_entity: '﷼', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '682', - smallest_denomination: 5 + html_entity: "﷼", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "682", + smallest_denomination: 5, }, sbd: { priority: 100, - iso_code: 'SBD', - name: 'Solomon Islands Dollar', - symbol: '$', - disambiguate_symbol: 'SI$', - alternate_symbols: ['SI$'], - subunit: 'Cent', + iso_code: "SBD", + name: "Solomon Islands Dollar", + symbol: "$", + disambiguate_symbol: "SI$", + alternate_symbols: ["SI$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '090', - smallest_denomination: 10 + format: "%n %u", + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "090", + smallest_denomination: 10, }, scr: { priority: 100, - iso_code: 'SCR', - name: 'Seychellois Rupee', - symbol: '₨', - disambiguate_symbol: 'SRe', - alternate_symbols: ['SRe', 'SR'], - subunit: 'Cent', + iso_code: "SCR", + name: "Seychellois Rupee", + symbol: "₨", + disambiguate_symbol: "SRe", + alternate_symbols: ["SRe", "SR"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '₨', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '690', - smallest_denomination: 1 + format: "%n %u", + html_entity: "₨", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "690", + smallest_denomination: 1, }, sdg: { priority: 100, - iso_code: 'SDG', - name: 'Sudanese Pound', - symbol: '£', - disambiguate_symbol: 'SDG', + iso_code: "SDG", + name: "Sudanese Pound", + symbol: "£", + disambiguate_symbol: "SDG", alternate_symbols: [], - subunit: 'Piastre', + subunit: "Piastre", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '938', - smallest_denomination: 1 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "938", + smallest_denomination: 1, }, sek: { priority: 100, - iso_code: 'SEK', - name: 'Swedish Krona', - symbol: 'kr', - disambiguate_symbol: 'SEK', - alternate_symbols: [':-'], - subunit: 'Öre', + iso_code: "SEK", + name: "Swedish Krona", + symbol: "kr", + disambiguate_symbol: "SEK", + alternate_symbols: [":-"], + subunit: "Öre", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: ',', - thousands_separator: ' ', - iso_numeric: '752', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ",", + thousands_separator: " ", + iso_numeric: "752", + smallest_denomination: 100, }, sgd: { priority: 100, - iso_code: 'SGD', - name: 'Singapore Dollar', - symbol: '$', - disambiguate_symbol: 'S$', - alternate_symbols: ['S$'], - subunit: 'Cent', + iso_code: "SGD", + name: "Singapore Dollar", + symbol: "$", + disambiguate_symbol: "S$", + alternate_symbols: ["S$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '702', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "702", + smallest_denomination: 1, }, shp: { priority: 100, - iso_code: 'SHP', - name: 'Saint Helenian Pound', - symbol: '£', - disambiguate_symbol: 'SHP', + iso_code: "SHP", + name: "Saint Helenian Pound", + symbol: "£", + disambiguate_symbol: "SHP", alternate_symbols: [], - subunit: 'Penny', + subunit: "Penny", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '£', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '654', - smallest_denomination: 1 + format: "%n %u", + html_entity: "£", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "654", + smallest_denomination: 1, }, skk: { priority: 100, - iso_code: 'SKK', - name: 'Slovak Koruna', - symbol: 'Sk', + iso_code: "SKK", + name: "Slovak Koruna", + symbol: "Sk", alternate_symbols: [], - subunit: 'Halier', + subunit: "Halier", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '703', - smallest_denomination: 50 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "703", + smallest_denomination: 50, }, sle: { priority: 100, - iso_code: 'SLE', - name: 'New Leone', - symbol: 'Le', + iso_code: "SLE", + name: "New Leone", + symbol: "Le", alternate_symbols: [], - subunit: 'Cent', + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '925', - smallest_denomination: 1000 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "925", + smallest_denomination: 1000, }, sll: { priority: 100, - iso_code: 'SLL', - name: 'Sierra Leonean Leone', - symbol: 'Le', - disambiguate_symbol: 'SLL', + iso_code: "SLL", + name: "Sierra Leonean Leone", + symbol: "Le", + disambiguate_symbol: "SLL", alternate_symbols: [], - subunit: 'Cent', + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '694', - smallest_denomination: 1000 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "694", + smallest_denomination: 1000, }, sos: { priority: 100, - iso_code: 'SOS', - name: 'Somali Shilling', - symbol: 'Sh', - alternate_symbols: ['Sh.So'], - subunit: 'Cent', + iso_code: "SOS", + name: "Somali Shilling", + symbol: "Sh", + alternate_symbols: ["Sh.So"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '706', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "706", + smallest_denomination: 1, }, srd: { priority: 100, - iso_code: 'SRD', - name: 'Surinamese Dollar', - symbol: '$', - disambiguate_symbol: 'SRD', + iso_code: "SRD", + name: "Surinamese Dollar", + symbol: "$", + disambiguate_symbol: "SRD", alternate_symbols: [], - subunit: 'Cent', + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '968', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "968", + smallest_denomination: 1, }, ssp: { priority: 100, - iso_code: 'SSP', - name: 'South Sudanese Pound', - symbol: '£', - disambiguate_symbol: 'SSP', + iso_code: "SSP", + name: "South Sudanese Pound", + symbol: "£", + disambiguate_symbol: "SSP", alternate_symbols: [], - subunit: 'piaster', + subunit: "piaster", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '£', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '728', - smallest_denomination: 5 + format: "%n %u", + html_entity: "£", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "728", + smallest_denomination: 5, }, std: { priority: 100, - iso_code: 'STD', - name: 'São Tomé and Príncipe Dobra', - symbol: 'Db', + iso_code: "STD", + name: "São Tomé and Príncipe Dobra", + symbol: "Db", alternate_symbols: [], - subunit: 'Cêntimo', + subunit: "Cêntimo", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '678', - smallest_denomination: 10000 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "678", + smallest_denomination: 10000, }, stn: { priority: 100, - iso_code: 'STN', - name: 'São Tomé and Príncipe Second Dobra', - symbol: 'Db', - disambiguate_symbol: 'STN', + iso_code: "STN", + name: "São Tomé and Príncipe Second Dobra", + symbol: "Db", + disambiguate_symbol: "STN", alternate_symbols: [], - subunit: 'Cêntimo', + subunit: "Cêntimo", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '930', - smallest_denomination: 10 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "930", + smallest_denomination: 10, }, svc: { priority: 100, - iso_code: 'SVC', - name: 'Salvadoran Colón', - symbol: '₡', - alternate_symbols: ['¢'], - subunit: 'Centavo', + iso_code: "SVC", + name: "Salvadoran Colón", + symbol: "₡", + alternate_symbols: ["¢"], + subunit: "Centavo", subunit_to_unit: 100, symbol_first: true, - html_entity: '₡', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '222', - smallest_denomination: 1 + html_entity: "₡", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "222", + smallest_denomination: 1, }, syp: { priority: 100, - iso_code: 'SYP', - name: 'Syrian Pound', - symbol: '£S', - alternate_symbols: ['£', 'ل.س', 'LS', 'الليرة السورية'], - subunit: 'Piastre', + iso_code: "SYP", + name: "Syrian Pound", + symbol: "£S", + alternate_symbols: ["£", "ل.س", "LS", "الليرة السورية"], + subunit: "Piastre", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '£', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '760', - smallest_denomination: 100 + format: "%n %u", + html_entity: "£", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "760", + smallest_denomination: 100, }, szl: { priority: 100, - iso_code: 'SZL', - name: 'Swazi Lilangeni', - symbol: 'E', - disambiguate_symbol: 'SZL', - subunit: 'Cent', + iso_code: "SZL", + name: "Swazi Lilangeni", + symbol: "E", + disambiguate_symbol: "SZL", + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '748', - smallest_denomination: 1 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "748", + smallest_denomination: 1, }, thb: { priority: 100, - iso_code: 'THB', - name: 'Thai Baht', - symbol: '฿', + iso_code: "THB", + name: "Thai Baht", + symbol: "฿", alternate_symbols: [], - subunit: 'Satang', + subunit: "Satang", subunit_to_unit: 100, symbol_first: true, - html_entity: '฿', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '764', - smallest_denomination: 1 + html_entity: "฿", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "764", + smallest_denomination: 1, }, tjs: { priority: 100, - iso_code: 'TJS', - name: 'Tajikistani Somoni', - symbol: 'ЅМ', + iso_code: "TJS", + name: "Tajikistani Somoni", + symbol: "ЅМ", alternate_symbols: [], - subunit: 'Diram', + subunit: "Diram", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '972', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "972", + smallest_denomination: 1, }, tmt: { priority: 100, - iso_code: 'TMT', - name: 'Turkmenistani Manat', - symbol: 'T', + iso_code: "TMT", + name: "Turkmenistani Manat", + symbol: "T", alternate_symbols: [], - subunit: 'Tenge', + subunit: "Tenge", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '934', - smallest_denomination: 1 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "934", + smallest_denomination: 1, }, tnd: { priority: 100, - iso_code: 'TND', - name: 'Tunisian Dinar', - symbol: 'د.ت', - alternate_symbols: ['TD', 'DT'], - subunit: 'Millime', + iso_code: "TND", + name: "Tunisian Dinar", + symbol: "د.ت", + alternate_symbols: ["TD", "DT"], + subunit: "Millime", subunit_to_unit: 1000, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '788', - smallest_denomination: 10 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "788", + smallest_denomination: 10, }, top: { priority: 100, - iso_code: 'TOP', - name: 'Tongan Paʻanga', - symbol: 'T$', - alternate_symbols: ['PT'], - subunit: 'Seniti', + iso_code: "TOP", + name: "Tongan Paʻanga", + symbol: "T$", + alternate_symbols: ["PT"], + subunit: "Seniti", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '776', - smallest_denomination: 1 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "776", + smallest_denomination: 1, }, try: { priority: 100, - iso_code: 'TRY', - name: 'Turkish Lira', - symbol: '₺', - alternate_symbols: ['TL'], - subunit: 'kuruş', + iso_code: "TRY", + name: "Turkish Lira", + symbol: "₺", + alternate_symbols: ["TL"], + subunit: "kuruş", subunit_to_unit: 100, symbol_first: true, - html_entity: '₺', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '949', - smallest_denomination: 1 + html_entity: "₺", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "949", + smallest_denomination: 1, }, ttd: { priority: 100, - iso_code: 'TTD', - name: 'Trinidad and Tobago Dollar', - symbol: '$', - disambiguate_symbol: 'TT$', - alternate_symbols: ['TT$'], - subunit: 'Cent', + iso_code: "TTD", + name: "Trinidad and Tobago Dollar", + symbol: "$", + disambiguate_symbol: "TT$", + alternate_symbols: ["TT$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '780', - smallest_denomination: 1 + format: "%n %u", + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "780", + smallest_denomination: 1, }, twd: { priority: 100, - iso_code: 'TWD', - name: 'New Taiwan Dollar', - symbol: '$', - disambiguate_symbol: 'NT$', - alternate_symbols: ['NT$'], - subunit: 'Cent', + iso_code: "TWD", + name: "New Taiwan Dollar", + symbol: "$", + disambiguate_symbol: "NT$", + alternate_symbols: ["NT$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '901', - smallest_denomination: 50 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "901", + smallest_denomination: 50, }, tzs: { priority: 100, - iso_code: 'TZS', - name: 'Tanzanian Shilling', - symbol: 'Sh', + iso_code: "TZS", + name: "Tanzanian Shilling", + symbol: "Sh", alternate_symbols: [], - subunit: 'Cent', + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '834', - smallest_denomination: 5000 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "834", + smallest_denomination: 5000, }, uah: { priority: 100, - iso_code: 'UAH', - name: 'Ukrainian Hryvnia', - symbol: '₴', + iso_code: "UAH", + name: "Ukrainian Hryvnia", + symbol: "₴", alternate_symbols: [], - subunit: 'Kopiyka', + subunit: "Kopiyka", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '₴', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '980', - smallest_denomination: 1 + format: "%n %u", + html_entity: "₴", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "980", + smallest_denomination: 1, }, ugx: { priority: 100, - iso_code: 'UGX', - name: 'Ugandan Shilling', - symbol: 'USh', + iso_code: "UGX", + name: "Ugandan Shilling", + symbol: "USh", alternate_symbols: [], - subunit: 'Cent', + subunit: "Cent", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '800', - smallest_denomination: 1000 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "800", + smallest_denomination: 1000, }, usd: { priority: 1, - iso_code: 'USD', - name: 'United States Dollar', - symbol: '$', - disambiguate_symbol: 'US$', - alternate_symbols: ['US$'], - subunit: 'Cent', + iso_code: "USD", + name: "United States Dollar", + symbol: "$", + disambiguate_symbol: "US$", + alternate_symbols: ["US$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '840', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "840", + smallest_denomination: 1, }, uyu: { priority: 100, - iso_code: 'UYU', - name: 'Uruguayan Peso', - symbol: '$U', - alternate_symbols: ['$U'], - subunit: 'Centésimo', + iso_code: "UYU", + name: "Uruguayan Peso", + symbol: "$U", + alternate_symbols: ["$U"], + subunit: "Centésimo", subunit_to_unit: 100, symbol_first: true, - html_entity: '$U', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '858', - smallest_denomination: 100 + html_entity: "$U", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "858", + smallest_denomination: 100, }, uzs: { priority: 100, - iso_code: 'UZS', - name: 'Uzbekistan Som', + iso_code: "UZS", + name: "Uzbekistan Som", symbol: "so'm", - alternate_symbols: ['so‘m', 'сўм', 'сум', 's', 'с'], - subunit: 'Tiyin', + alternate_symbols: ["so‘m", "сўм", "сум", "s", "с"], + subunit: "Tiyin", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '860', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "860", + smallest_denomination: 100, }, ves: { priority: 100, - iso_code: 'VES', - name: 'Venezuelan Bolívar Soberano', - symbol: 'Bs', - alternate_symbols: ['Bs.S'], - subunit: 'Céntimo', + iso_code: "VES", + name: "Venezuelan Bolívar Soberano", + symbol: "Bs", + alternate_symbols: ["Bs.S"], + subunit: "Céntimo", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '928', - smallest_denomination: 1 + html_entity: "", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "928", + smallest_denomination: 1, }, vnd: { priority: 100, - iso_code: 'VND', - name: 'Vietnamese Đồng', - symbol: '₫', + iso_code: "VND", + name: "Vietnamese Đồng", + symbol: "₫", alternate_symbols: [], - subunit: 'Hào', + subunit: "Hào", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '₫', - decimal_mark: ',', - thousands_separator: '.', - iso_numeric: '704', - smallest_denomination: 100 + format: "%n %u", + html_entity: "₫", + decimal_mark: ",", + thousands_separator: ".", + iso_numeric: "704", + smallest_denomination: 100, }, vuv: { priority: 100, - iso_code: 'VUV', - name: 'Vanuatu Vatu', - symbol: 'Vt', + iso_code: "VUV", + name: "Vanuatu Vatu", + symbol: "Vt", alternate_symbols: [], subunit: null, subunit_to_unit: 1, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '548', - smallest_denomination: 1 + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "548", + smallest_denomination: 1, }, wst: { priority: 100, - iso_code: 'WST', - name: 'Samoan Tala', - symbol: 'T', - disambiguate_symbol: 'WS$', - alternate_symbols: ['WS$', 'SAT', 'ST'], - subunit: 'Sene', + iso_code: "WST", + name: "Samoan Tala", + symbol: "T", + disambiguate_symbol: "WS$", + alternate_symbols: ["WS$", "SAT", "ST"], + subunit: "Sene", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '882', - smallest_denomination: 10 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "882", + smallest_denomination: 10, }, xaf: { priority: 100, - iso_code: 'XAF', - name: 'Central African Cfa Franc', - symbol: 'CFA', - disambiguate_symbol: 'FCFA', - alternate_symbols: ['FCFA'], - subunit: 'Centime', + iso_code: "XAF", + name: "Central African Cfa Franc", + symbol: "CFA", + disambiguate_symbol: "FCFA", + alternate_symbols: ["FCFA"], + subunit: "Centime", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '950', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "950", + smallest_denomination: 100, }, xag: { priority: 100, - iso_code: 'XAG', - name: 'Silver (Troy Ounce)', - symbol: 'oz t', - disambiguate_symbol: 'XAG', + iso_code: "XAG", + name: "Silver (Troy Ounce)", + symbol: "oz t", + disambiguate_symbol: "XAG", alternate_symbols: [], - subunit: 'oz', + subunit: "oz", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '961' + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "961", }, xau: { priority: 100, - iso_code: 'XAU', - name: 'Gold (Troy Ounce)', - symbol: 'oz t', - disambiguate_symbol: 'XAU', + iso_code: "XAU", + name: "Gold (Troy Ounce)", + symbol: "oz t", + disambiguate_symbol: "XAU", alternate_symbols: [], - subunit: 'oz', + subunit: "oz", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '959' + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "959", }, xba: { priority: 100, - iso_code: 'XBA', - name: 'European Composite Unit', - symbol: '', - disambiguate_symbol: 'XBA', + iso_code: "XBA", + name: "European Composite Unit", + symbol: "", + disambiguate_symbol: "XBA", alternate_symbols: [], - subunit: '', + subunit: "", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '955' + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "955", }, xbb: { priority: 100, - iso_code: 'XBB', - name: 'European Monetary Unit', - symbol: '', - disambiguate_symbol: 'XBB', + iso_code: "XBB", + name: "European Monetary Unit", + symbol: "", + disambiguate_symbol: "XBB", alternate_symbols: [], - subunit: '', + subunit: "", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '956' + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "956", }, xbc: { priority: 100, - iso_code: 'XBC', - name: 'European Unit of Account 9', - symbol: '', - disambiguate_symbol: 'XBC', + iso_code: "XBC", + name: "European Unit of Account 9", + symbol: "", + disambiguate_symbol: "XBC", alternate_symbols: [], - subunit: '', + subunit: "", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '957' + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "957", }, xbd: { priority: 100, - iso_code: 'XBD', - name: 'European Unit of Account 17', - symbol: '', - disambiguate_symbol: 'XBD', + iso_code: "XBD", + name: "European Unit of Account 17", + symbol: "", + disambiguate_symbol: "XBD", alternate_symbols: [], - subunit: '', + subunit: "", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '958' + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "958", }, xcd: { priority: 100, - iso_code: 'XCD', - name: 'East Caribbean Dollar', - symbol: '$', - disambiguate_symbol: 'EX$', - alternate_symbols: ['EC$'], - subunit: 'Cent', + iso_code: "XCD", + name: "East Caribbean Dollar", + symbol: "$", + disambiguate_symbol: "EX$", + alternate_symbols: ["EC$"], + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '951', - smallest_denomination: 1 + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "951", + smallest_denomination: 1, }, xdr: { priority: 100, - iso_code: 'XDR', - name: 'Special Drawing Rights', - symbol: 'SDR', - alternate_symbols: ['XDR'], - subunit: '', + iso_code: "XDR", + name: "Special Drawing Rights", + symbol: "SDR", + alternate_symbols: ["XDR"], + subunit: "", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '$', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '960' + format: "%n %u", + html_entity: "$", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "960", }, xof: { priority: 100, - iso_code: 'XOF', - name: 'West African Cfa Franc', - symbol: 'Fr', - disambiguate_symbol: 'CFA', - alternate_symbols: ['CFA'], - subunit: 'Centime', + iso_code: "XOF", + name: "West African Cfa Franc", + symbol: "Fr", + disambiguate_symbol: "CFA", + alternate_symbols: ["CFA"], + subunit: "Centime", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '952', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "952", + smallest_denomination: 100, }, xpd: { priority: 100, - iso_code: 'XPD', - name: 'Palladium', - symbol: 'oz t', - disambiguate_symbol: 'XPD', + iso_code: "XPD", + name: "Palladium", + symbol: "oz t", + disambiguate_symbol: "XPD", alternate_symbols: [], - subunit: 'oz', + subunit: "oz", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '964' + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "964", }, xpf: { priority: 100, - iso_code: 'XPF', - name: 'Cfp Franc', - symbol: 'Fr', - alternate_symbols: ['F'], - subunit: 'Centime', + iso_code: "XPF", + name: "Cfp Franc", + symbol: "Fr", + alternate_symbols: ["F"], + subunit: "Centime", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '953', - smallest_denomination: 100 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "953", + smallest_denomination: 100, }, xpt: { priority: 100, - iso_code: 'XPT', - name: 'Platinum', - symbol: 'oz t', + iso_code: "XPT", + name: "Platinum", + symbol: "oz t", alternate_symbols: [], - subunit: '', + subunit: "", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '962', - smallest_denomination: '' + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "962", + smallest_denomination: "", }, xts: { priority: 100, - iso_code: 'XTS', - name: 'Codes specifically reserved for testing purposes', - symbol: '', + iso_code: "XTS", + name: "Codes specifically reserved for testing purposes", + symbol: "", alternate_symbols: [], - subunit: '', + subunit: "", subunit_to_unit: 1, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '963', - smallest_denomination: '' + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "963", + smallest_denomination: "", }, yer: { priority: 100, - iso_code: 'YER', - name: 'Yemeni Rial', - symbol: '﷼', + iso_code: "YER", + name: "Yemeni Rial", + symbol: "﷼", alternate_symbols: [], - subunit: 'Fils', + subunit: "Fils", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '﷼', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '886', - smallest_denomination: 100 + format: "%n %u", + html_entity: "﷼", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "886", + smallest_denomination: 100, }, zar: { priority: 100, - iso_code: 'ZAR', - name: 'South African Rand', - symbol: 'R', + iso_code: "ZAR", + name: "South African Rand", + symbol: "R", alternate_symbols: [], - subunit: 'Cent', + subunit: "Cent", subunit_to_unit: 100, symbol_first: true, - html_entity: 'R', - decimal_mark: ',', - thousands_separator: ' ', - iso_numeric: '710', - smallest_denomination: 10 + html_entity: "R", + decimal_mark: ",", + thousands_separator: " ", + iso_numeric: "710", + smallest_denomination: 10, }, zmk: { priority: 100, - iso_code: 'ZMK', - name: 'Zambian Kwacha', - symbol: 'ZK', - disambiguate_symbol: 'ZMK', + iso_code: "ZMK", + name: "Zambian Kwacha", + symbol: "ZK", + disambiguate_symbol: "ZMK", alternate_symbols: [], - subunit: 'Ngwee', + subunit: "Ngwee", subunit_to_unit: 100, symbol_first: false, - format: '%n %u', - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '894', - smallest_denomination: 5 + format: "%n %u", + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "894", + smallest_denomination: 5, }, zmw: { priority: 100, - iso_code: 'ZMW', - name: 'Zambian Kwacha', - symbol: 'K', - disambiguate_symbol: 'ZMW', + iso_code: "ZMW", + name: "Zambian Kwacha", + symbol: "K", + disambiguate_symbol: "ZMW", alternate_symbols: [], - subunit: 'Ngwee', + subunit: "Ngwee", subunit_to_unit: 100, symbol_first: true, - html_entity: '', - decimal_mark: '.', - thousands_separator: ',', - iso_numeric: '967', - smallest_denomination: 5 - } + html_entity: "", + decimal_mark: ".", + thousands_separator: ",", + iso_numeric: "967", + smallest_denomination: 5, + }, } satisfies Record /** @@ -2737,18 +2737,18 @@ export const currencies = { * The list is grouped by the top currencies and the rest of the currencies. */ export const currencyInputSelectOptions: GroupedSelectValues = (() => { - const topCurrencies = ['USD', 'EUR', 'GBP'] + const topCurrencies = ["USD", "EUR", "GBP"] return [ { - options: topCurrencies.map((code) => ({ value: code, label: code })) + options: topCurrencies.map((code) => ({ value: code, label: code })), }, { options: Object.entries(currencies) .map(([, item]) => item.iso_code) .filter((code) => !topCurrencies.includes(code)) .map((code) => ({ value: code, label: code })) - .sort((a, b) => a.label.localeCompare(b.label)) - } + .sort((a, b) => a.label.localeCompare(b.label)), + }, ] })() diff --git a/packages/app-elements/src/helpers/date.test.ts b/packages/app-elements/src/helpers/date.test.ts index fefa7f5eb..8494931b6 100644 --- a/packages/app-elements/src/helpers/date.test.ts +++ b/packages/app-elements/src/helpers/date.test.ts @@ -1,745 +1,745 @@ import { makeDateYearsRange, - removeMillisecondsFromIsoDate -} from '#helpers/date' + removeMillisecondsFromIsoDate, +} from "#helpers/date" import { formatDate, formatDateRange, formatDateWithPredicate, getEventDateInfo, getIsoDateAtDayEdge, - getIsoDateAtDaysBefore -} from './date' + getIsoDateAtDaysBefore, +} from "./date" -describe('formatDate', () => { +describe("formatDate", () => { beforeEach(() => { - vi.useFakeTimers().setSystemTime('2023-12-25T14:30:00.000Z') + vi.useFakeTimers().setSystemTime("2023-12-25T14:30:00.000Z") }) afterEach(() => { vi.useRealTimers() }) - test('Should not break when date format is wrong', () => { + test("Should not break when date format is wrong", () => { expect( - formatDate({ isoDate: '20221T15:35:42.315Z', locale: 'en-US' }) - ).toBe('N/A') + formatDate({ isoDate: "20221T15:35:42.315Z", locale: "en-US" }), + ).toBe("N/A") }) - test('Should not break if date is empty string', () => { - expect(formatDate({ isoDate: '', locale: 'en-US' })).toBe('N/A') + test("Should not break if date is empty string", () => { + expect(formatDate({ isoDate: "", locale: "en-US" })).toBe("N/A") }) - test('Should not break if no date is passed', () => { - expect(formatDate({ isoDate: undefined, locale: 'en-US' })).toBe('N/A') + test("Should not break if no date is passed", () => { + expect(formatDate({ isoDate: undefined, locale: "en-US" })).toBe("N/A") }) - test('Should return a nice date string', () => { + test("Should return a nice date string", () => { expect( formatDate({ - isoDate: '2022-10-14T14:32:00.000Z', - locale: 'en-US' - }) - ).toBe('Oct 14, 2022') + isoDate: "2022-10-14T14:32:00.000Z", + locale: "en-US", + }), + ).toBe("Oct 14, 2022") }) - test('Should return the value without the year when current year', () => { + test("Should return the value without the year when current year", () => { expect( formatDate({ - isoDate: '2023-10-14T14:32:00.000Z', - format: 'date', - locale: 'en-US' - }) - ).toBe('Oct 14') + isoDate: "2023-10-14T14:32:00.000Z", + format: "date", + locale: "en-US", + }), + ).toBe("Oct 14") }) - test('Should return the value with the year when previous year', () => { + test("Should return the value with the year when previous year", () => { expect( formatDate({ - isoDate: '2022-10-14T14:32:00.000Z', - format: 'date', - locale: 'en-US' - }) - ).toBe('Oct 14, 2022') + isoDate: "2022-10-14T14:32:00.000Z", + format: "date", + locale: "en-US", + }), + ).toBe("Oct 14, 2022") }) test('Should return "Today" when today', () => { expect( formatDate({ - isoDate: '2023-12-25T14:32:00.000Z', - format: 'date', - locale: 'en-US' - }) - ).toBe('Today') + isoDate: "2023-12-25T14:32:00.000Z", + format: "date", + locale: "en-US", + }), + ).toBe("Today") }) test('Should return "00:32" as time', () => { expect( formatDate({ - isoDate: '2023-12-25T00:32:00.000Z', - format: 'fullWithSeconds', + isoDate: "2023-12-25T00:32:00.000Z", + format: "fullWithSeconds", showCurrentYear: true, - locale: 'en-US' - }) - ).toContain('00:32') + locale: "en-US", + }), + ).toContain("00:32") }) - test('Should return a date with time', () => { + test("Should return a date with time", () => { // AM expect( formatDate({ - isoDate: '2023-02-22T10:32:47.284Z', - format: 'full', - locale: 'en-US' - }) - ).toBe('Feb 22, 10:32') + isoDate: "2023-02-22T10:32:47.284Z", + format: "full", + locale: "en-US", + }), + ).toBe("Feb 22, 10:32") // PM expect( formatDate({ - isoDate: '2022-10-26T16:16:31.279Z', - format: 'full', - locale: 'en-US' - }) - ).toBe('Oct 26, 2022, 16:16') + isoDate: "2022-10-26T16:16:31.279Z", + format: "full", + locale: "en-US", + }), + ).toBe("Oct 26, 2022, 16:16") }) - test('Should return a date with time and seconds', () => { + test("Should return a date with time and seconds", () => { // AM expect( formatDate({ - isoDate: '2023-02-22T10:32:47.284Z', - format: 'fullWithSeconds', - locale: 'en-US' - }) - ).toBe('Feb 22, 10:32:47') + isoDate: "2023-02-22T10:32:47.284Z", + format: "fullWithSeconds", + locale: "en-US", + }), + ).toBe("Feb 22, 10:32:47") // PM expect( formatDate({ - isoDate: '2022-10-26T16:16:31.279Z', - format: 'fullWithSeconds', - locale: 'en-US' - }) - ).toBe('Oct 26, 2022, 16:16:31') + isoDate: "2022-10-26T16:16:31.279Z", + format: "fullWithSeconds", + locale: "en-US", + }), + ).toBe("Oct 26, 2022, 16:16:31") }) - test('Should return only the time', () => { + test("Should return only the time", () => { // AM expect( formatDate({ - isoDate: '2023-02-22T05:32:47.284Z', - format: 'time', - locale: 'en-US' - }) - ).toBe('05:32') + isoDate: "2023-02-22T05:32:47.284Z", + format: "time", + locale: "en-US", + }), + ).toBe("05:32") // PM expect( formatDate({ - isoDate: '2022-10-26T16:16:31.279Z', - format: 'time', - locale: 'en-US' - }) - ).toBe('16:16') + isoDate: "2022-10-26T16:16:31.279Z", + format: "time", + locale: "en-US", + }), + ).toBe("16:16") }) - test('Should return only the time with seconds', () => { + test("Should return only the time with seconds", () => { // AM expect( formatDate({ - isoDate: '2023-02-22T10:32:47.284Z', - format: 'timeWithSeconds', - locale: 'en-US' - }) - ).toBe('10:32:47') + isoDate: "2023-02-22T10:32:47.284Z", + format: "timeWithSeconds", + locale: "en-US", + }), + ).toBe("10:32:47") // PM expect( formatDate({ - isoDate: '2022-10-26T16:16:31.279Z', - format: 'timeWithSeconds', - locale: 'en-US' - }) - ).toBe('16:16:31') + isoDate: "2022-10-26T16:16:31.279Z", + format: "timeWithSeconds", + locale: "en-US", + }), + ).toBe("16:16:31") }) - test('Should accept a specific timezone override', () => { + test("Should accept a specific timezone override", () => { // Sydney (day after) expect( formatDate({ - isoDate: '2022-10-26T16:16:31.279Z', - timezone: 'Australia/Sydney', - format: 'date', - locale: 'en-US' - }) - ).toBe('Oct 27, 2022') + isoDate: "2022-10-26T16:16:31.279Z", + timezone: "Australia/Sydney", + format: "date", + locale: "en-US", + }), + ).toBe("Oct 27, 2022") // Rome expect( formatDate({ - isoDate: '2022-10-26T16:16:31.279Z', - timezone: 'Europe/Rome', - format: 'full', - locale: 'en-US' - }) - ).toBe('Oct 26, 2022, 18:16') + isoDate: "2022-10-26T16:16:31.279Z", + timezone: "Europe/Rome", + format: "full", + locale: "en-US", + }), + ).toBe("Oct 26, 2022, 18:16") }) - test('Should return the distance to now', () => { + test("Should return the distance to now", () => { expect( formatDate({ - isoDate: '2023-12-25T14:29:40.000Z', - timezone: 'Australia/Sydney', - format: 'distanceToNow', - locale: 'en-US' - }) - ).toBe('less than a minute ago') + isoDate: "2023-12-25T14:29:40.000Z", + timezone: "Australia/Sydney", + format: "distanceToNow", + locale: "en-US", + }), + ).toBe("less than a minute ago") expect( formatDate({ - isoDate: '2023-12-25T14:29:40.000Z', - timezone: 'Europe/Rome', - format: 'distanceToNow', - locale: 'en-US' - }) - ).toBe('less than a minute ago') + isoDate: "2023-12-25T14:29:40.000Z", + timezone: "Europe/Rome", + format: "distanceToNow", + locale: "en-US", + }), + ).toBe("less than a minute ago") expect( formatDate({ - isoDate: '2023-12-25T14:30:10.000Z', - timezone: 'Europe/Rome', - format: 'distanceToNow', - locale: 'en-US' - }) - ).toBe('in less than a minute') + isoDate: "2023-12-25T14:30:10.000Z", + timezone: "Europe/Rome", + format: "distanceToNow", + locale: "en-US", + }), + ).toBe("in less than a minute") expect( formatDate({ - isoDate: '2023-12-25T14:23:00.000Z', - timezone: 'Europe/Rome', - format: 'distanceToNow', - locale: 'en-US' - }) - ).toBe('7 minutes ago') + isoDate: "2023-12-25T14:23:00.000Z", + timezone: "Europe/Rome", + format: "distanceToNow", + locale: "en-US", + }), + ).toBe("7 minutes ago") expect( formatDate({ - isoDate: '2023-12-25T14:37:00.000Z', - timezone: 'Europe/Rome', - format: 'distanceToNow', - locale: 'en-US' - }) - ).toBe('in 7 minutes') + isoDate: "2023-12-25T14:37:00.000Z", + timezone: "Europe/Rome", + format: "distanceToNow", + locale: "en-US", + }), + ).toBe("in 7 minutes") expect( formatDate({ - isoDate: '2023-02-27T16:00:00.000Z', - format: 'distanceToNow', - locale: 'en-US' - }) - ).toBe('10 months ago') + isoDate: "2023-02-27T16:00:00.000Z", + format: "distanceToNow", + locale: "en-US", + }), + ).toBe("10 months ago") expect( formatDate({ - isoDate: '2024-10-27T16:00:00.000Z', - format: 'distanceToNow', - locale: 'en-US' - }) - ).toBe('in 10 months') + isoDate: "2024-10-27T16:00:00.000Z", + format: "distanceToNow", + locale: "en-US", + }), + ).toBe("in 10 months") }) }) -describe('formatDateWithPredicate', () => { +describe("formatDateWithPredicate", () => { beforeEach(() => { - vi.useFakeTimers().setSystemTime('2023-12-25T14:30:00.000Z') + vi.useFakeTimers().setSystemTime("2023-12-25T14:30:00.000Z") }) afterEach(() => { vi.useRealTimers() }) - test('Should return a nice date string with predicate', () => { + test("Should return a nice date string with predicate", () => { expect( formatDateWithPredicate({ - predicate: 'Created', - isoDate: '2022-10-14T14:32:00.000Z', - locale: 'en-US' - }) - ).toBe('Created on Oct 14, 2022, 14:32') + predicate: "Created", + isoDate: "2022-10-14T14:32:00.000Z", + locale: "en-US", + }), + ).toBe("Created on Oct 14, 2022, 14:32") }) - test('Should return the predicate followed by `on` and the date without the year when current year', () => { + test("Should return the predicate followed by `on` and the date without the year when current year", () => { expect( formatDateWithPredicate({ - predicate: 'Updated', - isoDate: '2023-10-14T14:32:00.000Z', - format: 'date', - locale: 'en-US' - }) - ).toBe('Updated on Oct 14') + predicate: "Updated", + isoDate: "2023-10-14T14:32:00.000Z", + format: "date", + locale: "en-US", + }), + ).toBe("Updated on Oct 14") }) test('Should return the predicate followed by just "today" when date is today', () => { expect( formatDateWithPredicate({ - predicate: 'Created', - isoDate: '2023-12-25T14:32:00.000Z', - format: 'date', - locale: 'en-US' - }) - ).toBe('Created today') + predicate: "Created", + isoDate: "2023-12-25T14:32:00.000Z", + format: "date", + locale: "en-US", + }), + ).toBe("Created today") }) - test('Should return the predicate followed by `on` and the date with time', () => { + test("Should return the predicate followed by `on` and the date with time", () => { expect( formatDateWithPredicate({ - predicate: 'Updated', - isoDate: '2023-02-22T10:32:47.284Z', - format: 'full', - locale: 'en-US' - }) - ).toBe('Updated on Feb 22, 10:32') + predicate: "Updated", + isoDate: "2023-02-22T10:32:47.284Z", + format: "full", + locale: "en-US", + }), + ).toBe("Updated on Feb 22, 10:32") }) - test('Should return the predicate followed by `on` and the date with time and seconds', () => { + test("Should return the predicate followed by `on` and the date with time and seconds", () => { expect( formatDateWithPredicate({ - predicate: 'Updated', - isoDate: '2023-02-22T10:32:47.284Z', - format: 'fullWithSeconds', - locale: 'en-US' - }) - ).toBe('Updated on Feb 22, 10:32:47') + predicate: "Updated", + isoDate: "2023-02-22T10:32:47.284Z", + format: "fullWithSeconds", + locale: "en-US", + }), + ).toBe("Updated on Feb 22, 10:32:47") }) - test('Should return the predicate followed by `at` and the time', () => { + test("Should return the predicate followed by `at` and the time", () => { expect( formatDateWithPredicate({ - predicate: 'Updated', - isoDate: '2023-02-22T05:32:47.284Z', - format: 'time', - locale: 'en-US' - }) - ).toBe('Updated at 05:32') + predicate: "Updated", + isoDate: "2023-02-22T05:32:47.284Z", + format: "time", + locale: "en-US", + }), + ).toBe("Updated at 05:32") }) - test('Should return the predicate followed by just the distance to now', () => { + test("Should return the predicate followed by just the distance to now", () => { expect( formatDateWithPredicate({ - predicate: 'Updated', - isoDate: '2023-12-25T14:29:50.000Z', - timezone: 'Australia/Sydney', - format: 'distanceToNow', - locale: 'en-US' - }) - ).toBe('Updated less than a minute ago') + predicate: "Updated", + isoDate: "2023-12-25T14:29:50.000Z", + timezone: "Australia/Sydney", + format: "distanceToNow", + locale: "en-US", + }), + ).toBe("Updated less than a minute ago") expect( formatDateWithPredicate({ - predicate: 'Expires', - isoDate: '2023-12-25T14:30:10.000Z', - timezone: 'Australia/Sydney', - format: 'distanceToNow', - locale: 'en-US' - }) - ).toBe('Expires in less than a minute') + predicate: "Expires", + isoDate: "2023-12-25T14:30:10.000Z", + timezone: "Australia/Sydney", + format: "distanceToNow", + locale: "en-US", + }), + ).toBe("Expires in less than a minute") }) }) -describe('getIsoDateAtDayEdge', () => { - test('should set start of the day in Los Angeles', () => { +describe("getIsoDateAtDayEdge", () => { + test("should set start of the day in Los Angeles", () => { expect( getIsoDateAtDayEdge({ - isoString: '2023-02-17T10:31:28.454Z', - edge: 'startOfTheDay', - timezone: 'America/Los_Angeles' - }) - ).toBe('2023-02-17T08:00:00.000Z') + isoString: "2023-02-17T10:31:28.454Z", + edge: "startOfTheDay", + timezone: "America/Los_Angeles", + }), + ).toBe("2023-02-17T08:00:00.000Z") }) - test('should set start of the day in Rome', () => { + test("should set start of the day in Rome", () => { expect( getIsoDateAtDayEdge({ - isoString: '2023-02-17T10:31:28.454Z', - edge: 'startOfTheDay', - timezone: 'Europe/Rome' - }) - ).toBe('2023-02-16T23:00:00.000Z') + isoString: "2023-02-17T10:31:28.454Z", + edge: "startOfTheDay", + timezone: "Europe/Rome", + }), + ).toBe("2023-02-16T23:00:00.000Z") }) - test('should set start of the day in Rome', () => { + test("should set start of the day in Rome", () => { expect( getIsoDateAtDayEdge({ - isoString: '2023-04-17T10:31:28.454Z', - edge: 'startOfTheDay', - timezone: 'Europe/Rome' - }) - ).toBe('2023-04-16T22:00:00.000Z') + isoString: "2023-04-17T10:31:28.454Z", + edge: "startOfTheDay", + timezone: "Europe/Rome", + }), + ).toBe("2023-04-16T22:00:00.000Z") }) - test('should set end of the day in Rome', () => { + test("should set end of the day in Rome", () => { expect( getIsoDateAtDayEdge({ - isoString: '2023-02-17T09:31:28.454Z', - edge: 'endOfTheDay', - timezone: 'Europe/Rome' - }) - ).toBe('2023-02-17T22:59:59.999Z') + isoString: "2023-02-17T09:31:28.454Z", + edge: "endOfTheDay", + timezone: "Europe/Rome", + }), + ).toBe("2023-02-17T22:59:59.999Z") }) - test('should work with partial dates', () => { + test("should work with partial dates", () => { expect( getIsoDateAtDayEdge({ - isoString: '2023-02-17', - edge: 'endOfTheDay', - timezone: 'Europe/Rome' - }) - ).toBe('2023-02-17T22:59:59.999Z') + isoString: "2023-02-17", + edge: "endOfTheDay", + timezone: "Europe/Rome", + }), + ).toBe("2023-02-17T22:59:59.999Z") }) - test('should return undefined when a no-date is passed', () => { + test("should return undefined when a no-date is passed", () => { expect( getIsoDateAtDayEdge({ - isoString: '', - edge: 'endOfTheDay', - timezone: 'Europe/Rome' - }) + isoString: "", + edge: "endOfTheDay", + timezone: "Europe/Rome", + }), ).toBe(undefined) expect( getIsoDateAtDayEdge({ - isoString: 'abcddds', - edge: 'endOfTheDay', - timezone: 'Europe/Rome' - }) + isoString: "abcddds", + edge: "endOfTheDay", + timezone: "Europe/Rome", + }), ).toBe(undefined) }) }) -describe('getIsoDateAtDaysBefore', () => { - test('should subtract days in default utc timezone', () => { +describe("getIsoDateAtDaysBefore", () => { + test("should subtract days in default utc timezone", () => { expect( getIsoDateAtDaysBefore({ - isoString: '2023-04-06T10:31:28.454Z', - days: 2 - }) - ).toBe('2023-04-04T00:00:00.000Z') + isoString: "2023-04-06T10:31:28.454Z", + days: 2, + }), + ).toBe("2023-04-04T00:00:00.000Z") }) - test('should subtract days from a date with custom timezone', () => { + test("should subtract days from a date with custom timezone", () => { // rome expect( getIsoDateAtDaysBefore({ - isoString: '2023-04-06T10:31:28.454Z', + isoString: "2023-04-06T10:31:28.454Z", days: 3, - timezone: 'Europe/Rome' - }) - ).toBe('2023-04-02T22:00:00.000Z') + timezone: "Europe/Rome", + }), + ).toBe("2023-04-02T22:00:00.000Z") // sydney expect( getIsoDateAtDaysBefore({ - isoString: '2023-04-06T10:31:28.454Z', + isoString: "2023-04-06T10:31:28.454Z", days: 2, - timezone: 'Australia/Sydney' - }) - ).toBe('2023-04-03T14:00:00.000Z') + timezone: "Australia/Sydney", + }), + ).toBe("2023-04-03T14:00:00.000Z") }) - test('should work also with the exact start of the utc day', () => { + test("should work also with the exact start of the utc day", () => { expect( getIsoDateAtDaysBefore({ - isoString: '2023-04-06T00:00:00.000Z', - days: 1 - }) - ).toBe('2023-04-05T00:00:00.000Z') + isoString: "2023-04-06T00:00:00.000Z", + days: 1, + }), + ).toBe("2023-04-05T00:00:00.000Z") }) - test('should work also with the exact end of the utc day', () => { + test("should work also with the exact end of the utc day", () => { expect( getIsoDateAtDaysBefore({ - isoString: '2023-04-06T23:59:59.999Z', - days: 1 - }) - ).toBe('2023-04-05T00:00:00.000Z') + isoString: "2023-04-06T23:59:59.999Z", + days: 1, + }), + ).toBe("2023-04-05T00:00:00.000Z") }) - test('should properly work with days above month length', () => { + test("should properly work with days above month length", () => { expect( getIsoDateAtDaysBefore({ - isoString: '2023-02-06T13:40:00.000Z', + isoString: "2023-02-06T13:40:00.000Z", days: 45, - timezone: 'Europe/Rome' - }) - ).toBe('2022-12-22T23:00:00.000Z') + timezone: "Europe/Rome", + }), + ).toBe("2022-12-22T23:00:00.000Z") expect( getIsoDateAtDaysBefore({ - isoString: '2023-02-06T10:20:30.325Z', + isoString: "2023-02-06T10:20:30.325Z", days: 365, - timezone: 'Australia/Sydney' - }) - ).toBe('2022-02-05T13:00:00.000Z') + timezone: "Australia/Sydney", + }), + ).toBe("2022-02-05T13:00:00.000Z") }) - test('should not accept negative days', () => { + test("should not accept negative days", () => { expect( getIsoDateAtDaysBefore({ - isoString: '2023-04-06T23:59:59.999Z', - days: -10 - }) + isoString: "2023-04-06T23:59:59.999Z", + days: -10, + }), ).toBe(undefined) }) - test('should not break with invalid date', () => { + test("should not break with invalid date", () => { expect( getIsoDateAtDaysBefore({ - isoString: 'sdsdsds', - days: 7 - }) + isoString: "sdsdsds", + days: 7, + }), ).toBe(undefined) }) }) -describe('getEventDateInfo', () => { +describe("getEventDateInfo", () => { beforeEach(() => { - vi.useFakeTimers().setSystemTime('2023-12-25T14:30:00.000Z') + vi.useFakeTimers().setSystemTime("2023-12-25T14:30:00.000Z") }) afterEach(() => { vi.useRealTimers() }) - test('should throw an error when the startsAt date comes after the expiresAt date', () => { + test("should throw an error when the startsAt date comes after the expiresAt date", () => { expect(() => { getEventDateInfo({ - startsAt: '2024-01-31T14:30:00.000Z', - expiresAt: '2024-01-01T14:30:00.000Z' + startsAt: "2024-01-31T14:30:00.000Z", + expiresAt: "2024-01-01T14:30:00.000Z", }) }).toThrowError( - 'The expiration date/time of the event must be after the activation (startsAt).' + "The expiration date/time of the event must be after the activation (startsAt).", ) }) test('should return "upcoming" when the event is in the future', () => { expect( getEventDateInfo({ - startsAt: '2024-01-01T14:30:00.000Z', - expiresAt: '2024-01-31T14:30:00.000Z' - }) - ).toEqual('upcoming') + startsAt: "2024-01-01T14:30:00.000Z", + expiresAt: "2024-01-31T14:30:00.000Z", + }), + ).toEqual("upcoming") }) test('should return "expired" when the event is in the past', () => { expect( getEventDateInfo({ - startsAt: '2023-01-01T14:30:00.000Z', - expiresAt: '2023-01-31T14:30:00.000Z' - }) - ).toEqual('past') + startsAt: "2023-01-01T14:30:00.000Z", + expiresAt: "2023-01-31T14:30:00.000Z", + }), + ).toEqual("past") }) test('should return "active" when the event is actually happening', () => { expect( getEventDateInfo({ - startsAt: '2023-12-01T14:30:00.000Z', - expiresAt: '2023-12-31T14:30:00.000Z' - }) - ).toEqual('active') + startsAt: "2023-12-01T14:30:00.000Z", + expiresAt: "2023-12-31T14:30:00.000Z", + }), + ).toEqual("active") }) }) -describe('formatDateRange should return the proper date format', () => { +describe("formatDateRange should return the proper date format", () => { beforeEach(() => { - vi.useFakeTimers().setSystemTime('2024-01-10T14:30:00.000Z') + vi.useFakeTimers().setSystemTime("2024-01-10T14:30:00.000Z") }) afterEach(() => { vi.useRealTimers() }) - test('when current year and different months', () => { + test("when current year and different months", () => { expect( formatDateRange({ - rangeFrom: '2024-01-01T14:30:00.000Z', - rangeTo: '2024-02-29T14:30:00.000Z', - locale: 'en-US' - }) - ).toEqual('Jan 01 - Feb 29') + rangeFrom: "2024-01-01T14:30:00.000Z", + rangeTo: "2024-02-29T14:30:00.000Z", + locale: "en-US", + }), + ).toEqual("Jan 01 - Feb 29") }) - test('when current year and same month', () => { + test("when current year and same month", () => { expect( formatDateRange({ - rangeFrom: '2024-01-01T14:30:00.000Z', - rangeTo: '2024-01-31T14:30:00.000Z', - locale: 'en-US' - }) - ).toEqual('1-31 Jan') + rangeFrom: "2024-01-01T14:30:00.000Z", + rangeTo: "2024-01-31T14:30:00.000Z", + locale: "en-US", + }), + ).toEqual("1-31 Jan") }) - test('when different year and different month', () => { + test("when different year and different month", () => { expect( formatDateRange({ - rangeFrom: '2023-01-01T14:30:00.000Z', - rangeTo: '2023-02-28T14:30:00.000Z', - locale: 'en-US' - }) - ).toEqual('Jan 01, 2023 - Feb 28, 2023') + rangeFrom: "2023-01-01T14:30:00.000Z", + rangeTo: "2023-02-28T14:30:00.000Z", + locale: "en-US", + }), + ).toEqual("Jan 01, 2023 - Feb 28, 2023") }) - test('when different year and same month', () => { + test("when different year and same month", () => { expect( formatDateRange({ - rangeFrom: '2023-01-01T14:30:00.000Z', - rangeTo: '2023-01-31T14:30:00.000Z', - locale: 'en-US' - }) - ).toEqual('1-31 Jan, 2023') + rangeFrom: "2023-01-01T14:30:00.000Z", + rangeTo: "2023-01-31T14:30:00.000Z", + locale: "en-US", + }), + ).toEqual("1-31 Jan, 2023") }) - test('when past year, current year and same month', () => { + test("when past year, current year and same month", () => { expect( formatDateRange({ - rangeFrom: '2023-01-01T14:30:00.000Z', - rangeTo: '2024-01-31T14:30:00.000Z', - locale: 'en-US' - }) - ).toEqual('Jan 01, 2023 - Jan 31') + rangeFrom: "2023-01-01T14:30:00.000Z", + rangeTo: "2024-01-31T14:30:00.000Z", + locale: "en-US", + }), + ).toEqual("Jan 01, 2023 - Jan 31") }) - test('when past year and current year', () => { + test("when past year and current year", () => { expect( formatDateRange({ - rangeFrom: '2023-12-01T14:30:00.000Z', - rangeTo: '2024-01-31T14:30:00.000Z', - locale: 'en-US' - }) - ).toEqual('Dec 01, 2023 - Jan 31') + rangeFrom: "2023-12-01T14:30:00.000Z", + rangeTo: "2024-01-31T14:30:00.000Z", + locale: "en-US", + }), + ).toEqual("Dec 01, 2023 - Jan 31") }) - test('when current year and future year', () => { + test("when current year and future year", () => { expect( formatDateRange({ - rangeFrom: '2024-12-01T14:30:00.000Z', - rangeTo: '2025-01-31T14:30:00.000Z', - locale: 'en-US' - }) - ).toEqual('Dec 01 - Jan 31, 2025') + rangeFrom: "2024-12-01T14:30:00.000Z", + rangeTo: "2025-01-31T14:30:00.000Z", + locale: "en-US", + }), + ).toEqual("Dec 01 - Jan 31, 2025") }) - test('when past year and future year', () => { + test("when past year and future year", () => { expect( formatDateRange({ - rangeFrom: '2023-12-01T14:30:00.000Z', - rangeTo: '2025-01-31T14:30:00.000Z', - locale: 'en-US' - }) - ).toEqual('Dec 01, 2023 - Jan 31, 2025') + rangeFrom: "2023-12-01T14:30:00.000Z", + rangeTo: "2025-01-31T14:30:00.000Z", + locale: "en-US", + }), + ).toEqual("Dec 01, 2023 - Jan 31, 2025") }) - test('with timezone Italy in winter', () => { + test("with timezone Italy in winter", () => { expect( formatDateRange({ - rangeFrom: '2024-02-04T23:00:00.000Z', - rangeTo: '2024-02-05T22:59:59.000Z', - timezone: 'Europe/Rome', - zonedAlready: true - }) - ).toEqual('5-5 Feb') + rangeFrom: "2024-02-04T23:00:00.000Z", + rangeTo: "2024-02-05T22:59:59.000Z", + timezone: "Europe/Rome", + zonedAlready: true, + }), + ).toEqual("5-5 Feb") }) - test('with timezone Italy in summer', () => { + test("with timezone Italy in summer", () => { expect( formatDateRange({ - rangeFrom: '2024-05-04T22:00:00.000Z', - rangeTo: '2024-05-05T21:59:59.000Z', - timezone: 'Europe/Rome' - }) - ).toEqual('5-5 May') + rangeFrom: "2024-05-04T22:00:00.000Z", + rangeTo: "2024-05-05T21:59:59.000Z", + timezone: "Europe/Rome", + }), + ).toEqual("5-5 May") }) - test('with timezone Italy in winter (more than 1 day)', () => { + test("with timezone Italy in winter (more than 1 day)", () => { expect( formatDateRange({ - rangeFrom: '2024-12-10T00:00:00.000Z', - rangeTo: '2024-12-10T23:59:59.999Z', - timezone: 'Europe/Rome' - }) - ).toEqual('10-11 Dec') + rangeFrom: "2024-12-10T00:00:00.000Z", + rangeTo: "2024-12-10T23:59:59.999Z", + timezone: "Europe/Rome", + }), + ).toEqual("10-11 Dec") }) - test('with timezone Dublin', () => { + test("with timezone Dublin", () => { expect( formatDateRange({ - rangeFrom: '2024-12-10T00:00:00.000Z', - rangeTo: '2024-12-10T23:59:59.999Z', - timezone: 'Europe/Dublin' - }) - ).toEqual('10-10 Dec') + rangeFrom: "2024-12-10T00:00:00.000Z", + rangeTo: "2024-12-10T23:59:59.999Z", + timezone: "Europe/Dublin", + }), + ).toEqual("10-10 Dec") }) }) -describe('makeDateYearsRange', () => { - test('should return last year range with milliseconds', () => { - const now = new Date('2023-04-24T13:45:00.000Z') +describe("makeDateYearsRange", () => { + test("should return last year range with milliseconds", () => { + const now = new Date("2023-04-24T13:45:00.000Z") const result = makeDateYearsRange({ now, showMilliseconds: true, - yearsAgo: 1 + yearsAgo: 1, }) expect(result).toEqual({ - date_from: '2022-04-24T13:45:01.000Z', - date_to: '2023-04-24T13:45:00.000Z' + date_from: "2022-04-24T13:45:01.000Z", + date_to: "2023-04-24T13:45:00.000Z", }) }) - test('should return last year range without milliseconds', () => { - const now = new Date('2023-04-24T13:45:00.538Z') + test("should return last year range without milliseconds", () => { + const now = new Date("2023-04-24T13:45:00.538Z") const result = makeDateYearsRange({ now, showMilliseconds: false, - yearsAgo: 1 + yearsAgo: 1, }) expect(result).toEqual({ - date_from: '2022-04-24T13:45:01Z', - date_to: '2023-04-24T13:45:00Z' + date_from: "2022-04-24T13:45:01Z", + date_to: "2023-04-24T13:45:00Z", }) }) - test('should return 5 years range without milliseconds', () => { - const now = new Date('2023-04-24T13:45:00.538Z') + test("should return 5 years range without milliseconds", () => { + const now = new Date("2023-04-24T13:45:00.538Z") const result = makeDateYearsRange({ now, showMilliseconds: false, - yearsAgo: 5 + yearsAgo: 5, }) expect(result).toEqual({ - date_from: '2018-04-24T13:45:01Z', - date_to: '2023-04-24T13:45:00Z' + date_from: "2018-04-24T13:45:01Z", + date_to: "2023-04-24T13:45:00Z", }) }) }) -describe('removeMillisecondsFromIsoDate', () => { - test('should remove milliseconds from the date', () => { - expect(removeMillisecondsFromIsoDate('2023-04-24T13:45:00.538Z')).toBe( - '2023-04-24T13:45:00Z' +describe("removeMillisecondsFromIsoDate", () => { + test("should remove milliseconds from the date", () => { + expect(removeMillisecondsFromIsoDate("2023-04-24T13:45:00.538Z")).toBe( + "2023-04-24T13:45:00Z", ) }) - test('should accept partial date string', () => { - expect(removeMillisecondsFromIsoDate('2023-03-25')).toBe( - '2023-03-25T00:00:00Z' + test("should accept partial date string", () => { + expect(removeMillisecondsFromIsoDate("2023-03-25")).toBe( + "2023-03-25T00:00:00Z", ) }) - test('should return same value if argument is invalid', () => { - expect(removeMillisecondsFromIsoDate('2023-broken-date')).toBe( - '2023-broken-date' + test("should return same value if argument is invalid", () => { + expect(removeMillisecondsFromIsoDate("2023-broken-date")).toBe( + "2023-broken-date", ) }) }) diff --git a/packages/app-elements/src/helpers/date.ts b/packages/app-elements/src/helpers/date.ts index 633a72adb..c66bea276 100644 --- a/packages/app-elements/src/helpers/date.ts +++ b/packages/app-elements/src/helpers/date.ts @@ -1,31 +1,31 @@ -import { type I18NLocale } from '#providers/I18NProvider' -import { formatInTimeZone } from 'date-fns-tz/formatInTimeZone' -import { fromZonedTime } from 'date-fns-tz/fromZonedTime' -import { toZonedTime } from 'date-fns-tz/toZonedTime' -import { endOfDay } from 'date-fns/endOfDay' -import { format } from 'date-fns/format' -import { formatDistance } from 'date-fns/formatDistance' -import { isBefore } from 'date-fns/isBefore' -import { isFuture } from 'date-fns/isFuture' -import { isPast } from 'date-fns/isPast' -import { isSameMonth } from 'date-fns/isSameMonth' -import { isSameYear } from 'date-fns/isSameYear' -import { isThisYear } from 'date-fns/isThisYear' -import { isToday } from 'date-fns/isToday' -import { it, type Locale } from 'date-fns/locale' -import { startOfDay } from 'date-fns/startOfDay' -import { sub } from 'date-fns/sub' -import groupBy from 'lodash-es/groupBy' -import orderBy from 'lodash-es/orderBy' -import { type Simplify } from 'type-fest' +import { endOfDay } from "date-fns/endOfDay" +import { format } from "date-fns/format" +import { formatDistance } from "date-fns/formatDistance" +import { isBefore } from "date-fns/isBefore" +import { isFuture } from "date-fns/isFuture" +import { isPast } from "date-fns/isPast" +import { isSameMonth } from "date-fns/isSameMonth" +import { isSameYear } from "date-fns/isSameYear" +import { isThisYear } from "date-fns/isThisYear" +import { isToday } from "date-fns/isToday" +import { it, type Locale } from "date-fns/locale" +import { startOfDay } from "date-fns/startOfDay" +import { sub } from "date-fns/sub" +import { formatInTimeZone } from "date-fns-tz/formatInTimeZone" +import { fromZonedTime } from "date-fns-tz/fromZonedTime" +import { toZonedTime } from "date-fns-tz/toZonedTime" +import groupBy from "lodash-es/groupBy" +import orderBy from "lodash-es/orderBy" +import type { Simplify } from "type-fest" +import type { I18NLocale } from "#providers/I18NProvider" type Format = - | 'date' - | 'time' - | 'timeWithSeconds' - | 'full' - | 'fullWithSeconds' - | 'distanceToNow' + | "date" + | "time" + | "timeWithSeconds" + | "full" + | "fullWithSeconds" + | "distanceToNow" interface FormatDateOptions { /** * JavaScript ISO date string. Example '2022-10-06T11:59:30.371Z' @@ -65,13 +65,13 @@ interface FormatDateOptions { */ export function formatDate({ isoDate, - timezone = 'UTC', - locale = 'en-US', + timezone = "UTC", + locale = "en-US", showCurrentYear = false, ...opts }: FormatDateOptions): string { if (isoDate == null) { - return 'N/A' + return "N/A" } try { @@ -82,14 +82,14 @@ export function formatDate({ timezone, opts.format, showCurrentYear, - locale + locale, ) return format(zonedDate, formatTemplate, { - locale: getLocaleOption(locale) + locale: getLocaleOption(locale), }) } catch { - return 'N/A' + return "N/A" } } @@ -107,13 +107,13 @@ interface FormatDateWithPredicateOptions extends FormatDateOptions { */ function getDatePredicateSeparatorByFormat( format: Format, - locale: I18NLocale + locale: I18NLocale, ): string { switch (format) { - case 'distanceToNow': - return '' - case 'time': - case 'timeWithSeconds': + case "distanceToNow": + return "" + case "time": + case "timeWithSeconds": return `${i18n(locale).at} ` default: return `${i18n(locale).on} ` @@ -128,79 +128,79 @@ function getDatePredicateSeparatorByFormat( export function formatDateWithPredicate({ isoDate, timezone, - format = 'full', + format = "full", predicate, - locale = 'en-US' + locale = "en-US", }: FormatDateWithPredicateOptions): string { const todayText = i18n(locale).today const formattedDate = formatDate({ isoDate, timezone, format, - locale + locale, }) // Replace the first occurrence of 'Today' with 'today' in lowercase .replace(todayText, todayText.toLowerCase()) const separator = !formattedDate.includes(todayText.toLowerCase()) ? `${getDatePredicateSeparatorByFormat(format, locale)}` - : '' + : "" return `${predicate} ${separator}${formattedDate}` } -export const timeSeparator = ', ' +export const timeSeparator = ", " function getPresetFormatTemplate( zonedDate: Date, timezone: string, - format: Format = 'date', + format: Format = "date", showCurrentYear: boolean, - locale: I18NLocale + locale: I18NLocale, ): string { switch (format) { - case 'date': + case "date": return isToday(zonedDate) && !showCurrentYear ? `'${i18n(locale).today}'` : isThisYear(zonedDate) && !showCurrentYear - ? 'LLL dd' - : 'LLL dd, yyyy' - case 'time': - return 'HH:mm' - case 'timeWithSeconds': - return `${getPresetFormatTemplate(zonedDate, timezone, 'time', showCurrentYear, locale)}:ss` - case 'full': + ? "LLL dd" + : "LLL dd, yyyy" + case "time": + return "HH:mm" + case "timeWithSeconds": + return `${getPresetFormatTemplate(zonedDate, timezone, "time", showCurrentYear, locale)}:ss` + case "full": return `${getPresetFormatTemplate( zonedDate, timezone, - 'date', + "date", showCurrentYear, - locale + locale, )}${timeSeparator}${getPresetFormatTemplate( zonedDate, timezone, - 'time', + "time", showCurrentYear, - locale + locale, )}` - case 'fullWithSeconds': + case "fullWithSeconds": return `${getPresetFormatTemplate( zonedDate, timezone, - 'date', + "date", showCurrentYear, - locale + locale, )}${timeSeparator}${getPresetFormatTemplate( zonedDate, timezone, - 'timeWithSeconds', + "timeWithSeconds", showCurrentYear, - locale + locale, )}` - case 'distanceToNow': + case "distanceToNow": return `'${formatDistance(zonedDate, toZonedTime(new Date(), timezone), { addSuffix: true, - locale: getLocaleOption(locale) + locale: getLocaleOption(locale), })}'` } } @@ -218,10 +218,10 @@ type DateISOString = string export function getIsoDateAtDayEdge({ isoString, edge, - timezone = 'UTC' + timezone = "UTC", }: { isoString: DateISOString - edge: 'startOfTheDay' | 'endOfTheDay' + edge: "startOfTheDay" | "endOfTheDay" timezone?: string }): string | undefined { try { @@ -232,11 +232,11 @@ export function getIsoDateAtDayEdge({ const zonedDate = toZonedTime(date, timezone) - if (edge === 'startOfTheDay') { + if (edge === "startOfTheDay") { return fromZonedTime(startOfDay(zonedDate), timezone).toISOString() } - if (edge === 'endOfTheDay') { + if (edge === "endOfTheDay") { return fromZonedTime(endOfDay(zonedDate), timezone).toISOString() } @@ -263,7 +263,7 @@ export function getIsoDateAtDayEdge({ export function getIsoDateAtDaysBefore({ isoString, days, - timezone = 'UTC' + timezone = "UTC", }: { isoString: DateISOString days: number @@ -275,8 +275,8 @@ export function getIsoDateAtDaysBefore({ const startOfDay = getIsoDateAtDayEdge({ isoString, - edge: 'startOfTheDay', - timezone + edge: "startOfTheDay", + timezone, }) if (startOfDay == null) { @@ -292,7 +292,7 @@ export function getIsoDateAtDaysBefore({ export function getEventDateInfo({ startsAt, expiresAt, - timezone = 'UTC' + timezone = "UTC", }: { /** The activation date/time of the event (ISO date string. Example '2022-10-06T11:59:30.371Z'). */ startsAt: DateISOString @@ -300,25 +300,25 @@ export function getEventDateInfo({ expiresAt: DateISOString /** Set a specific timezone, when not passed default value is 'UTC' */ timezone?: string -}): 'active' | 'past' | 'upcoming' { +}): "active" | "past" | "upcoming" { const zonedStartsAt = toZonedTime(new Date(startsAt), timezone) const zonedExpiresAt = toZonedTime(new Date(expiresAt), timezone) if (isBefore(zonedExpiresAt, zonedStartsAt)) { throw new Error( - 'The expiration date/time of the event must be after the activation (startsAt).' + "The expiration date/time of the event must be after the activation (startsAt).", ) } if (isFuture(zonedStartsAt)) { - return 'upcoming' + return "upcoming" } if (isPast(zonedExpiresAt)) { - return 'past' + return "past" } - return 'active' + return "active" } /** @@ -328,8 +328,8 @@ export function getEventDateInfo({ export function formatDateRange({ rangeFrom, rangeTo, - timezone = 'UTC', - locale = 'en-US' + timezone = "UTC", + locale = "en-US", }: { /** JavaScript Date or ISO string. Example '2022-10-06T11:59:30.371Z' */ rangeFrom: DateISOString | Date @@ -345,16 +345,16 @@ export function formatDateRange({ rangeTo = new Date(rangeTo).toISOString() if (isSameYear(rangeFrom, rangeTo) && isSameMonth(rangeFrom, rangeTo)) { - const dayOfMonthFrom = formatInTimeZone(rangeFrom, timezone, 'd', { - locale: getLocaleOption(locale) + const dayOfMonthFrom = formatInTimeZone(rangeFrom, timezone, "d", { + locale: getLocaleOption(locale), }) - const dayOfMonthTo = formatInTimeZone(rangeTo, timezone, 'd', { - locale: getLocaleOption(locale) + const dayOfMonthTo = formatInTimeZone(rangeTo, timezone, "d", { + locale: getLocaleOption(locale), }) - const month = formatInTimeZone(rangeFrom, timezone, 'LLL', { - locale: getLocaleOption(locale) + const month = formatInTimeZone(rangeFrom, timezone, "LLL", { + locale: getLocaleOption(locale), }) - const year = isThisYear(rangeFrom) ? '' : `, ${format(rangeFrom, 'yyyy')}` + const year = isThisYear(rangeFrom) ? "" : `, ${format(rangeFrom, "yyyy")}` return `${dayOfMonthFrom}-${dayOfMonthTo} ${month}${year}` } @@ -369,7 +369,7 @@ export interface Event { date: string } -type Position = 'first' | 'other' +type Position = "first" | "other" /** * @@ -382,8 +382,8 @@ export function sortAndGroupByDate( { timezone, locale, - orders = 'desc' - }: { timezone?: string; locale?: I18NLocale; orders?: 'asc' | 'desc' } = {} + orders = "desc", + }: { timezone?: string; locale?: I18NLocale; orders?: "asc" | "desc" } = {}, ): Record< string, Array< @@ -396,23 +396,23 @@ export function sortAndGroupByDate( > { const ordered: Array = orderBy( events, - 'date', - orders + "date", + orders, ).map((event, index) => { - const position: Position = index === events.length - 1 ? 'first' : 'other' + const position: Position = index === events.length - 1 ? "first" : "other" return { ...event, - position + position, } }) return groupBy(ordered, (val) => formatDate({ isoDate: val.date, - format: 'date', + format: "date", timezone, - locale - }).toUpperCase() + locale, + }).toUpperCase(), ) } @@ -427,8 +427,8 @@ export function sortAndGroupByDate( export function removeMillisecondsFromIsoDate(isoDate: string): string { try { const validDate = new Date(isoDate) - return (validDate.toISOString().split('.')[0] ?? '') + 'Z' - } catch (e) { + return `${validDate.toISOString().split(".")[0] ?? ""}Z` + } catch (_e) { return isoDate } } @@ -458,7 +458,7 @@ export function removeMillisecondsFromIsoDate(isoDate: string): string { export function makeDateYearsRange({ now, yearsAgo, - showMilliseconds = true + showMilliseconds = true, }: { now: Date yearsAgo: number @@ -468,14 +468,14 @@ export function makeDateYearsRange({ date_to: string } { if (yearsAgo < 1) { - throw new Error('Years ago must be greater than 0') + throw new Error("Years ago must be greater than 0") } const to = now.toISOString() // same day, one year ago const lastYearDate = new Date( - new Date(now).setFullYear(now.getFullYear() - yearsAgo) + new Date(now).setFullYear(now.getFullYear() - yearsAgo), ) // remove 1 second to avoid overlapping with the current year lastYearDate.setSeconds(lastYearDate.getSeconds() + 1) @@ -483,7 +483,7 @@ export function makeDateYearsRange({ return { date_from: showMilliseconds ? from : removeMillisecondsFromIsoDate(from), - date_to: showMilliseconds ? to : removeMillisecondsFromIsoDate(to) + date_to: showMilliseconds ? to : removeMillisecondsFromIsoDate(to), } } @@ -492,30 +492,28 @@ export function makeDateYearsRange({ */ function getLocaleOption(locale: I18NLocale): Locale | undefined { switch (locale) { - case 'it-IT': + case "it-IT": return it - case 'en-US': default: return undefined } } -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type function i18n(localeCode: I18NLocale) { const locale = { en: { - today: 'Today', - at: 'at', - on: 'on' + today: "Today", + at: "at", + on: "on", }, it: { - today: 'Oggi', - at: 'alle', - on: 'il' - } + today: "Oggi", + at: "alle", + on: "il", + }, } - if (localeCode === 'it-IT') { + if (localeCode === "it-IT") { return locale.it } return locale.en @@ -527,5 +525,5 @@ function i18n(localeCode: I18NLocale) { * @returns True if the date is valid, false otherwise. */ export function isDateValid(date: Date): boolean { - return date instanceof Date && !isNaN(date.getTime()) + return date instanceof Date && !Number.isNaN(date.getTime()) } diff --git a/packages/app-elements/src/helpers/downloadJsonAsFile.ts b/packages/app-elements/src/helpers/downloadJsonAsFile.ts index 39716d9f4..59f256275 100644 --- a/packages/app-elements/src/helpers/downloadJsonAsFile.ts +++ b/packages/app-elements/src/helpers/downloadJsonAsFile.ts @@ -1,4 +1,4 @@ -import isEmpty from 'lodash-es/isEmpty' +import isEmpty from "lodash-es/isEmpty" /** * Trigger the download of requested JSON object as user defined file @@ -7,7 +7,7 @@ import isEmpty from 'lodash-es/isEmpty' */ export function downloadJsonAsFile({ json, - filename + filename, }: { json?: object filename: string @@ -15,11 +15,10 @@ export function downloadJsonAsFile({ if (isEmpty(json)) { json = {} } - const dataUri = - 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(json)) - const tag = document.createElement('a') - tag.setAttribute('href', dataUri) - tag.setAttribute('download', filename) + const dataUri = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(json))}` + const tag = document.createElement("a") + tag.setAttribute("href", dataUri) + tag.setAttribute("download", filename) document.body.appendChild(tag) tag.click() tag.remove() diff --git a/packages/app-elements/src/helpers/giftCards.test.ts b/packages/app-elements/src/helpers/giftCards.test.ts index 8c4b382e4..df127e3f1 100644 --- a/packages/app-elements/src/helpers/giftCards.test.ts +++ b/packages/app-elements/src/helpers/giftCards.test.ts @@ -1,25 +1,25 @@ -import { maskGiftCardCode } from './giftCards' +import { maskGiftCardCode } from "./giftCards" -describe('maskGiftCardCode', () => { +describe("maskGiftCardCode", () => { it('should return "N/A" if the code is null', () => { - expect(maskGiftCardCode(null)).toBe('N/A') + expect(maskGiftCardCode(null)).toBe("N/A") }) it('should return "N/A" if the code is undefined', () => { - expect(maskGiftCardCode(undefined)).toBe('N/A') + expect(maskGiftCardCode(undefined)).toBe("N/A") }) - it('should handle empty string as N/A', () => { - expect(maskGiftCardCode('')).toBe('N/A') + it("should handle empty string as N/A", () => { + expect(maskGiftCardCode("")).toBe("N/A") }) - it('should return the code if its length is 8 or less', () => { - expect(maskGiftCardCode('12345678')).toBe('12345678') - expect(maskGiftCardCode('1234')).toBe('1234') + it("should return the code if its length is 8 or less", () => { + expect(maskGiftCardCode("12345678")).toBe("12345678") + expect(maskGiftCardCode("1234")).toBe("1234") }) - it('should mask the code if its length is greater than 8', () => { - expect(maskGiftCardCode('123456789')).toBe('··23456789') - expect(maskGiftCardCode('abcdefghij')).toBe('··cdefghij') + it("should mask the code if its length is greater than 8", () => { + expect(maskGiftCardCode("123456789")).toBe("··23456789") + expect(maskGiftCardCode("abcdefghij")).toBe("··cdefghij") }) }) diff --git a/packages/app-elements/src/helpers/giftCards.ts b/packages/app-elements/src/helpers/giftCards.ts index 8d65d2285..d8a8debe8 100644 --- a/packages/app-elements/src/helpers/giftCards.ts +++ b/packages/app-elements/src/helpers/giftCards.ts @@ -1,6 +1,6 @@ export function maskGiftCardCode(code?: string | null): string { - if (code == null || code === '') { - return 'N/A' + if (code == null || code === "") { + return "N/A" } if (code.length <= 8) { diff --git a/packages/app-elements/src/helpers/mocks.test.ts b/packages/app-elements/src/helpers/mocks.test.ts index 0096f2030..ce506fec2 100644 --- a/packages/app-elements/src/helpers/mocks.test.ts +++ b/packages/app-elements/src/helpers/mocks.test.ts @@ -1,39 +1,39 @@ -import type { Resource } from '@commercelayer/sdk' -import { describe, expect, it } from 'vitest' -import { isMock, isMockedId } from './mocks' +import type { Resource } from "@commercelayer/sdk" +import { describe, expect, it } from "vitest" +import { isMock, isMockedId } from "./mocks" -describe('isMockedId', () => { +describe("isMockedId", () => { it('returns true for ids starting with "fake-"', () => { - expect(isMockedId('fake-123')).toBe(true) - expect(isMockedId('fake-abc')).toBe(true) - expect(isMockedId('fake-')).toBe(true) + expect(isMockedId("fake-123")).toBe(true) + expect(isMockedId("fake-abc")).toBe(true) + expect(isMockedId("fake-")).toBe(true) }) it('returns false for ids not starting with "fake-"', () => { - expect(isMockedId('real-123')).toBe(false) - expect(isMockedId('123-fake')).toBe(false) - expect(isMockedId('')).toBe(false) - expect(isMockedId('fak-e123')).toBe(false) + expect(isMockedId("real-123")).toBe(false) + expect(isMockedId("123-fake")).toBe(false) + expect(isMockedId("")).toBe(false) + expect(isMockedId("fak-e123")).toBe(false) }) }) -describe('isMock', () => { +describe("isMock", () => { it('returns true if resource.id starts with "fake-"', () => { const resource: Resource = { - id: 'fake-456', - type: 'orders', - created_at: '', - updated_at: '' + id: "fake-456", + type: "orders", + created_at: "", + updated_at: "", } expect(isMock(resource)).toBe(true) }) it('returns false if resource.id does not start with "fake-"', () => { const resource: Resource = { - id: 'order-789', - type: 'orders', - created_at: '', - updated_at: '' + id: "order-789", + type: "orders", + created_at: "", + updated_at: "", } expect(isMock(resource)).toBe(false) }) diff --git a/packages/app-elements/src/helpers/mocks.ts b/packages/app-elements/src/helpers/mocks.ts index 49dafc710..097293f47 100644 --- a/packages/app-elements/src/helpers/mocks.ts +++ b/packages/app-elements/src/helpers/mocks.ts @@ -1,7 +1,7 @@ -import { type Resource } from '@commercelayer/sdk' +import type { Resource } from "@commercelayer/sdk" export const isMockedId = (id: string): boolean => { - return id.startsWith('fake-') + return id.startsWith("fake-") } export const isMock = (resource: Resource): boolean => { diff --git a/packages/app-elements/src/helpers/name.test.ts b/packages/app-elements/src/helpers/name.test.ts index fd8b94ffa..5530f6b00 100644 --- a/packages/app-elements/src/helpers/name.test.ts +++ b/packages/app-elements/src/helpers/name.test.ts @@ -1,38 +1,38 @@ -import { computeFullname, formatDisplayName } from './name' +import { computeFullname, formatDisplayName } from "./name" -describe('formatDisplayName', () => { - test('should return empty string if both first and last name are empty', () => { - expect(formatDisplayName('', '')).toBe('') +describe("formatDisplayName", () => { + test("should return empty string if both first and last name are empty", () => { + expect(formatDisplayName("", "")).toBe("") }) - test('should return last name if first name is empty', () => { - expect(formatDisplayName('', 'Doe')).toBe('Doe') + test("should return last name if first name is empty", () => { + expect(formatDisplayName("", "Doe")).toBe("Doe") }) - test('should return first name if last name is empty', () => { - expect(formatDisplayName('John', '')).toBe('John') + test("should return first name if last name is empty", () => { + expect(formatDisplayName("John", "")).toBe("John") }) - test('should return fullname if first name is 1 char long', () => { - expect(formatDisplayName('J', 'Reed')).toBe('J Reed') + test("should return fullname if first name is 1 char long", () => { + expect(formatDisplayName("J", "Reed")).toBe("J Reed") }) - test('should return initial for first name in most of the cases', () => { - expect(formatDisplayName('John', 'Reed')).toBe('J. Reed') + test("should return initial for first name in most of the cases", () => { + expect(formatDisplayName("John", "Reed")).toBe("J. Reed") }) }) -describe('computeFullname', () => { - test('should return full name', () => { - expect(computeFullname('John', 'Reed')).toBe('John Reed') +describe("computeFullname", () => { + test("should return full name", () => { + expect(computeFullname("John", "Reed")).toBe("John Reed") }) - test('should trim white spaces', () => { - expect(computeFullname(' ', 'Reed ')).toBe('Reed') - expect(computeFullname('John', '')).toBe('John') + test("should trim white spaces", () => { + expect(computeFullname(" ", "Reed ")).toBe("Reed") + expect(computeFullname("John", "")).toBe("John") }) - test('should allow undefined values', () => { - expect(computeFullname('John')).toBe('John') + test("should allow undefined values", () => { + expect(computeFullname("John")).toBe("John") }) }) diff --git a/packages/app-elements/src/helpers/name.ts b/packages/app-elements/src/helpers/name.ts index 8fd4f936f..406bc931d 100644 --- a/packages/app-elements/src/helpers/name.ts +++ b/packages/app-elements/src/helpers/name.ts @@ -1,4 +1,4 @@ -import isEmpty from 'lodash-es/isEmpty' +import isEmpty from "lodash-es/isEmpty" /** * Make a display name from first and last name in the format of @@ -6,18 +6,18 @@ import isEmpty from 'lodash-es/isEmpty' */ export function formatDisplayName( firstName: string, - lastName?: string + lastName?: string, ): string { if (isEmpty(firstName) && isEmpty(lastName)) { - return '' + return "" } if (firstName == null || isEmpty(firstName)) { - return lastName ?? '' + return lastName ?? "" } if (lastName == null || isEmpty(lastName)) { - return firstName ?? '' + return firstName ?? "" } if (firstName.length === 1) { @@ -31,5 +31,5 @@ export function formatDisplayName( * Make a full name from first and last name in the format */ export function computeFullname(firstName: string, lastName?: string): string { - return `${firstName ?? ''} ${lastName ?? ''}`.trim() + return `${firstName ?? ""} ${lastName ?? ""}`.trim() } diff --git a/packages/app-elements/src/helpers/resources.test.ts b/packages/app-elements/src/helpers/resources.test.ts index 8ff6fe94e..5cdf453d0 100644 --- a/packages/app-elements/src/helpers/resources.test.ts +++ b/packages/app-elements/src/helpers/resources.test.ts @@ -1,101 +1,101 @@ -import { formatResourceName } from './resources' +import { formatResourceName } from "./resources" -describe('formatResourceName', () => { - test('should return singular lowercase as default', () => { - expect(formatResourceName({ resource: 'orders' })).toBe('order') - expect(formatResourceName({ resource: 'skus' })).toBe('SKU') +describe("formatResourceName", () => { + test("should return singular lowercase as default", () => { + expect(formatResourceName({ resource: "orders" })).toBe("order") + expect(formatResourceName({ resource: "skus" })).toBe("SKU") }) - test('should return singular for count 1 or `singular`', () => { - expect(formatResourceName({ resource: 'addresses', count: 1 })).toBe( - 'address' + test("should return singular for count 1 or `singular`", () => { + expect(formatResourceName({ resource: "addresses", count: 1 })).toBe( + "address", ) expect( - formatResourceName({ resource: 'shipments', count: 'singular' }) - ).toBe('shipment') + formatResourceName({ resource: "shipments", count: "singular" }), + ).toBe("shipment") }) - test('should return plural lowercase as default with count', () => { - expect(formatResourceName({ resource: 'orders', count: 10 })).toBe('orders') - expect(formatResourceName({ resource: 'skus', count: 'plural' })).toBe( - 'SKUs' + test("should return plural lowercase as default with count", () => { + expect(formatResourceName({ resource: "orders", count: 10 })).toBe("orders") + expect(formatResourceName({ resource: "skus", count: "plural" })).toBe( + "SKUs", ) expect( - formatResourceName({ resource: 'satispay_gateways', count: 'plural' }) - ).toBe('satispay gateways') + formatResourceName({ resource: "satispay_gateways", count: "plural" }), + ).toBe("satispay gateways") }) - test('should return plural when count is zero', () => { - expect(formatResourceName({ resource: 'attachments', count: 0 })).toBe( - 'attachments' + test("should return plural when count is zero", () => { + expect(formatResourceName({ resource: "attachments", count: 0 })).toBe( + "attachments", ) }) - test('should handle simple titlecase name', () => { + test("should handle simple titlecase name", () => { expect( formatResourceName({ - resource: 'orders', + resource: "orders", count: 10, - format: 'title' - }) - ).toBe('Orders') + format: "title", + }), + ).toBe("Orders") expect( formatResourceName({ - resource: 'checkout_com_payments', + resource: "checkout_com_payments", count: 10, - format: 'title' - }) - ).toBe('Checkout.com payments') + format: "title", + }), + ).toBe("Checkout.com payments") expect( formatResourceName({ - resource: 'checkout_com_payments', + resource: "checkout_com_payments", count: 1, - format: 'title' - }) - ).toBe('Checkout.com payment') + format: "title", + }), + ).toBe("Checkout.com payment") }) - test('should handle special title case for SKU wordings', () => { + test("should handle special title case for SKU wordings", () => { expect( formatResourceName({ - resource: 'sku_list_items', + resource: "sku_list_items", count: 1, - format: 'title' - }) - ).toBe('SKU list item') + format: "title", + }), + ).toBe("SKU list item") expect( formatResourceName({ - resource: 'sku_list_items', + resource: "sku_list_items", count: 10, - format: 'title' - }) - ).toBe('SKU list items') + format: "title", + }), + ).toBe("SKU list items") expect( formatResourceName({ - resource: 'skus', + resource: "skus", count: 1, - format: 'title' - }) - ).toBe('SKU') + format: "title", + }), + ).toBe("SKU") expect( formatResourceName({ - resource: 'skus', + resource: "skus", count: 10, - format: 'title' - }) - ).toBe('SKUs') + format: "title", + }), + ).toBe("SKUs") expect( formatResourceName({ - resource: 'sku_lists', - count: 'plural', - format: 'title' - }) - ).toBe('SKU lists') + resource: "sku_lists", + count: "plural", + format: "title", + }), + ).toBe("SKU lists") }) }) diff --git a/packages/app-elements/src/helpers/resources.ts b/packages/app-elements/src/helpers/resources.ts index e7800a03a..c3895408a 100644 --- a/packages/app-elements/src/helpers/resources.ts +++ b/packages/app-elements/src/helpers/resources.ts @@ -1,271 +1,271 @@ -import { - type ListableResourceType, - type ResourceTypeLock, - type ResourceUpdate -} from '@commercelayer/sdk' +import type { + ListableResourceType, + ResourceTypeLock, + ResourceUpdate, +} from "@commercelayer/sdk" const singularLowercase: Record = { - addresses: 'address', - adjustments: 'adjustment', - adyen_gateways: 'adyen gateway', - adyen_payments: 'adyen payment', - attachments: 'attachment', - authorizations: 'authorization', - avalara_accounts: 'avalara account', - axerve_gateways: 'axerve gateway', - axerve_payments: 'axerve payment', - bing_geocoders: 'bing geocoder', - braintree_gateways: 'braintree gateway', - braintree_payments: 'braintree payment', - bundles: 'bundle', - buy_x_pay_y_promotions: 'buy x pay y promotion', - captures: 'capture', - carrier_accounts: 'carrier account', - checkout_com_gateways: 'checkout.com gateway', - checkout_com_payments: 'checkout.com payment', - cleanups: 'cleanup', - coupon_codes_promotion_rules: 'coupon codes promotion rule', - coupon_recipients: 'coupon recipient', - coupons: 'coupon', - custom_promotion_rules: 'custom promotion rule', - customer_addresses: 'customer address', - customer_groups: 'customer group', - customer_password_resets: 'customer password reset', - customer_payment_sources: 'customer payment source', - customer_subscriptions: 'customer subscription', - customers: 'customer', - delivery_lead_times: 'delivery lead time', - discount_engine_items: 'discount engine item', - discount_engines: 'discount engine', - easypost_pickups: 'easypost pickup', - event_callbacks: 'event callback', - events: 'event', - exports: 'export', - external_gateways: 'external gateway', - external_payments: 'external payment', - external_promotions: 'external promotion', - external_tax_calculators: 'external tax calculator', - fixed_amount_promotions: 'fixed amount promotion', - fixed_price_promotions: 'fixed price promotion', - flex_promotions: 'flex promotion', - free_gift_promotions: 'free gift promotion', - free_shipping_promotions: 'free shipping promotion', - geocoders: 'geocoder', - gift_card_recipients: 'gift card recipient', - gift_cards: 'gift card', - google_geocoders: 'google geocoder', - imports: 'import', - in_stock_subscriptions: 'in stock subscription', - inventory_models: 'inventory model', - inventory_return_locations: 'inventory return location', - inventory_stock_locations: 'inventory stock location', - klarna_gateways: 'klarna gateway', - klarna_payments: 'klarna payment', - line_item_options: 'line item option', - line_items: 'line item', - links: 'link', - manual_gateways: 'manual gateway', - manual_tax_calculators: 'manual tax calculator', - markets: 'market', - merchants: 'merchant', - notifications: 'notification', - order_amount_promotion_rules: 'order amount promotion rule', - order_copies: 'order copy', - order_factories: 'order factory', - order_subscription_items: 'order subscription item', - order_subscriptions: 'order subscription', - orders: 'order', - packages: 'package', - parcel_line_items: 'parcel line item', - parcels: 'parcel', - payment_gateways: 'payment gateway', - payment_methods: 'payment method', - payment_options: 'payment option', - paypal_gateways: 'paypal gateway', - paypal_payments: 'paypal payment', - percentage_discount_promotions: 'percentage discount promotion', - pickups: 'pickup', - price_frequency_tiers: 'price frequency tier', - price_list_schedulers: 'price list scheduler', - price_lists: 'price list', - price_tiers: 'price tier', - price_volume_tiers: 'price volume tier', - prices: 'price', - promotion_rules: 'promotion rule', - promotions: 'promotion', - recurring_order_copies: 'recurring order copy', - refunds: 'refund', - reserved_stocks: 'reserved stock', - resource_errors: 'resource error', - return_line_items: 'return line item', - returns: 'return', - satispay_gateways: 'satispay gateway', - satispay_payments: 'satispay payment', - shipments: 'shipment', - shipping_categories: 'shipping category', - shipping_method_tiers: 'shipping method tier', - shipping_methods: 'shipping method', - shipping_weight_tiers: 'shipping weight tier', - shipping_zones: 'shipping zone', - sku_list_items: 'SKU list item', - sku_list_promotion_rules: 'SKU list promotion rule', - sku_lists: 'SKU list', - sku_options: 'SKU option', - skus: 'SKU', - stock_items: 'stock item', - stock_line_items: 'stock line item', - stock_locations: 'stock location', - stock_reservations: 'stock reservation', - stock_transfers: 'stock transfer', - stores: 'Store', - stripe_gateways: 'stripe gateway', - stripe_payments: 'stripe payment', - stripe_tax_accounts: 'stripe tax account', - subscription_models: 'subscription model', - tags: 'tag', - talon_one_accounts: 'talon.one account', - tax_calculators: 'tax calculator', - tax_categories: 'tax category', - tax_rules: 'tax rule', - taxjar_accounts: 'taxjar account', - transactions: 'transaction', - versions: 'version', - vertex_accounts: 'vertex account', - voids: 'void', - webhooks: 'webhook', - wire_transfers: 'wire transfer' + addresses: "address", + adjustments: "adjustment", + adyen_gateways: "adyen gateway", + adyen_payments: "adyen payment", + attachments: "attachment", + authorizations: "authorization", + avalara_accounts: "avalara account", + axerve_gateways: "axerve gateway", + axerve_payments: "axerve payment", + bing_geocoders: "bing geocoder", + braintree_gateways: "braintree gateway", + braintree_payments: "braintree payment", + bundles: "bundle", + buy_x_pay_y_promotions: "buy x pay y promotion", + captures: "capture", + carrier_accounts: "carrier account", + checkout_com_gateways: "checkout.com gateway", + checkout_com_payments: "checkout.com payment", + cleanups: "cleanup", + coupon_codes_promotion_rules: "coupon codes promotion rule", + coupon_recipients: "coupon recipient", + coupons: "coupon", + custom_promotion_rules: "custom promotion rule", + customer_addresses: "customer address", + customer_groups: "customer group", + customer_password_resets: "customer password reset", + customer_payment_sources: "customer payment source", + customer_subscriptions: "customer subscription", + customers: "customer", + delivery_lead_times: "delivery lead time", + discount_engine_items: "discount engine item", + discount_engines: "discount engine", + easypost_pickups: "easypost pickup", + event_callbacks: "event callback", + events: "event", + exports: "export", + external_gateways: "external gateway", + external_payments: "external payment", + external_promotions: "external promotion", + external_tax_calculators: "external tax calculator", + fixed_amount_promotions: "fixed amount promotion", + fixed_price_promotions: "fixed price promotion", + flex_promotions: "flex promotion", + free_gift_promotions: "free gift promotion", + free_shipping_promotions: "free shipping promotion", + geocoders: "geocoder", + gift_card_recipients: "gift card recipient", + gift_cards: "gift card", + google_geocoders: "google geocoder", + imports: "import", + in_stock_subscriptions: "in stock subscription", + inventory_models: "inventory model", + inventory_return_locations: "inventory return location", + inventory_stock_locations: "inventory stock location", + klarna_gateways: "klarna gateway", + klarna_payments: "klarna payment", + line_item_options: "line item option", + line_items: "line item", + links: "link", + manual_gateways: "manual gateway", + manual_tax_calculators: "manual tax calculator", + markets: "market", + merchants: "merchant", + notifications: "notification", + order_amount_promotion_rules: "order amount promotion rule", + order_copies: "order copy", + order_factories: "order factory", + order_subscription_items: "order subscription item", + order_subscriptions: "order subscription", + orders: "order", + packages: "package", + parcel_line_items: "parcel line item", + parcels: "parcel", + payment_gateways: "payment gateway", + payment_methods: "payment method", + payment_options: "payment option", + paypal_gateways: "paypal gateway", + paypal_payments: "paypal payment", + percentage_discount_promotions: "percentage discount promotion", + pickups: "pickup", + price_frequency_tiers: "price frequency tier", + price_list_schedulers: "price list scheduler", + price_lists: "price list", + price_tiers: "price tier", + price_volume_tiers: "price volume tier", + prices: "price", + promotion_rules: "promotion rule", + promotions: "promotion", + recurring_order_copies: "recurring order copy", + refunds: "refund", + reserved_stocks: "reserved stock", + resource_errors: "resource error", + return_line_items: "return line item", + returns: "return", + satispay_gateways: "satispay gateway", + satispay_payments: "satispay payment", + shipments: "shipment", + shipping_categories: "shipping category", + shipping_method_tiers: "shipping method tier", + shipping_methods: "shipping method", + shipping_weight_tiers: "shipping weight tier", + shipping_zones: "shipping zone", + sku_list_items: "SKU list item", + sku_list_promotion_rules: "SKU list promotion rule", + sku_lists: "SKU list", + sku_options: "SKU option", + skus: "SKU", + stock_items: "stock item", + stock_line_items: "stock line item", + stock_locations: "stock location", + stock_reservations: "stock reservation", + stock_transfers: "stock transfer", + stores: "Store", + stripe_gateways: "stripe gateway", + stripe_payments: "stripe payment", + stripe_tax_accounts: "stripe tax account", + subscription_models: "subscription model", + tags: "tag", + talon_one_accounts: "talon.one account", + tax_calculators: "tax calculator", + tax_categories: "tax category", + tax_rules: "tax rule", + taxjar_accounts: "taxjar account", + transactions: "transaction", + versions: "version", + vertex_accounts: "vertex account", + voids: "void", + webhooks: "webhook", + wire_transfers: "wire transfer", } const pluralLowercase: Record = { - addresses: 'addresses', - adjustments: 'adjustments', - adyen_gateways: 'adyen gateways', - adyen_payments: 'adyen payments', - attachments: 'attachments', - authorizations: 'authorizations', - avalara_accounts: 'avalara accounts', - axerve_gateways: 'axerve gateways', - axerve_payments: 'axerve payments', - bing_geocoders: 'bing geocoders', - braintree_gateways: 'braintree gateways', - braintree_payments: 'braintree payments', - bundles: 'bundles', - buy_x_pay_y_promotions: 'buy x pay y promotions', - captures: 'captures', - carrier_accounts: 'carrier accounts', - checkout_com_gateways: 'checkout.com gateways', - checkout_com_payments: 'checkout.com payments', - cleanups: 'cleanups', - coupon_codes_promotion_rules: 'coupon codes promotion rules', - coupon_recipients: 'coupon recipients', - coupons: 'coupons', - custom_promotion_rules: 'custom promotion rules', - customer_addresses: 'customer addresses', - customer_groups: 'customer groups', - customer_password_resets: 'customer password resets', - customer_payment_sources: 'customer payment sources', - customer_subscriptions: 'customer subscriptions', - customers: 'customers', - delivery_lead_times: 'delivery lead times', - discount_engine_items: 'discount engine items', - discount_engines: 'discount engines', - easypost_pickups: 'easypost pickups', - event_callbacks: 'event callbacks', - events: 'events', - exports: 'exports', - external_gateways: 'external gateways', - external_payments: 'external payments', - external_promotions: 'external promotions', - external_tax_calculators: 'external tax calculators', - fixed_amount_promotions: 'fixed amount promotions', - fixed_price_promotions: 'fixed price promotions', - flex_promotions: 'flex promotions', - free_gift_promotions: 'free gift promotions', - free_shipping_promotions: 'free shipping promotions', - geocoders: 'geocoders', - gift_card_recipients: 'gift card recipients', - gift_cards: 'gift cards', - google_geocoders: 'google geocoders', - imports: 'imports', - in_stock_subscriptions: 'in stock subscriptions', - inventory_models: 'inventory models', - inventory_return_locations: 'inventory return locations', - inventory_stock_locations: 'inventory stock locations', - klarna_gateways: 'klarna gateways', - klarna_payments: 'klarna payments', - line_item_options: 'line item options', - line_items: 'line items', - links: 'links', - manual_gateways: 'manual gateways', - manual_tax_calculators: 'manual tax calculators', - markets: 'markets', - merchants: 'merchants', - notifications: 'notifications', - order_amount_promotion_rules: 'order amount promotion rules', - order_copies: 'order copies', - order_factories: 'order factories', - order_subscription_items: 'order subscription items', - order_subscriptions: 'order subscriptions', - orders: 'orders', - packages: 'packages', - parcel_line_items: 'parcel line items', - parcels: 'parcels', - payment_gateways: 'payment gateways', - payment_methods: 'payment methods', - payment_options: 'payment options', - paypal_gateways: 'paypal gateways', - paypal_payments: 'paypal payments', - percentage_discount_promotions: 'percentage discount promotions', - pickups: 'pickups', - price_frequency_tiers: 'price frequency tiers', - price_list_schedulers: 'price list schedulers', - price_lists: 'price lists', - price_tiers: 'price tiers', - price_volume_tiers: 'price volume tiers', - prices: 'prices', - promotion_rules: 'promotion rules', - promotions: 'promotions', - recurring_order_copies: 'recurring order copies', - refunds: 'refunds', - reserved_stocks: 'reserved stocks', - resource_errors: 'resource errors', - return_line_items: 'return line items', - returns: 'returns', - satispay_gateways: 'satispay gateways', - satispay_payments: 'satispay payments', - shipments: 'shipments', - shipping_categories: 'shipping categories', - shipping_method_tiers: 'shipping method tiers', - shipping_methods: 'shipping methods', - shipping_weight_tiers: 'shipping weight tiers', - shipping_zones: 'shipping zones', - sku_list_items: 'SKU list items', - sku_list_promotion_rules: 'SKU list promotion rules', - sku_lists: 'SKU lists', - sku_options: 'SKU options', - skus: 'SKUs', - stock_items: 'stock items', - stock_line_items: 'stock line items', - stock_locations: 'stock locations', - stock_reservations: 'stock reservations', - stock_transfers: 'stock transfers', - stores: 'Stores', - stripe_gateways: 'stripe gateways', - stripe_payments: 'stripe payments', - stripe_tax_accounts: 'stripe tax accounts', - subscription_models: 'subscription models', - tags: 'tags', - talon_one_accounts: 'talon.one accounts', - tax_calculators: 'tax calculators', - tax_categories: 'tax categories', - tax_rules: 'tax rules', - taxjar_accounts: 'taxjar accounts', - transactions: 'transactions', - versions: 'versions', - vertex_accounts: 'vertex accounts', - voids: 'voids', - webhooks: 'webhooks', - wire_transfers: 'wire transfers' + addresses: "addresses", + adjustments: "adjustments", + adyen_gateways: "adyen gateways", + adyen_payments: "adyen payments", + attachments: "attachments", + authorizations: "authorizations", + avalara_accounts: "avalara accounts", + axerve_gateways: "axerve gateways", + axerve_payments: "axerve payments", + bing_geocoders: "bing geocoders", + braintree_gateways: "braintree gateways", + braintree_payments: "braintree payments", + bundles: "bundles", + buy_x_pay_y_promotions: "buy x pay y promotions", + captures: "captures", + carrier_accounts: "carrier accounts", + checkout_com_gateways: "checkout.com gateways", + checkout_com_payments: "checkout.com payments", + cleanups: "cleanups", + coupon_codes_promotion_rules: "coupon codes promotion rules", + coupon_recipients: "coupon recipients", + coupons: "coupons", + custom_promotion_rules: "custom promotion rules", + customer_addresses: "customer addresses", + customer_groups: "customer groups", + customer_password_resets: "customer password resets", + customer_payment_sources: "customer payment sources", + customer_subscriptions: "customer subscriptions", + customers: "customers", + delivery_lead_times: "delivery lead times", + discount_engine_items: "discount engine items", + discount_engines: "discount engines", + easypost_pickups: "easypost pickups", + event_callbacks: "event callbacks", + events: "events", + exports: "exports", + external_gateways: "external gateways", + external_payments: "external payments", + external_promotions: "external promotions", + external_tax_calculators: "external tax calculators", + fixed_amount_promotions: "fixed amount promotions", + fixed_price_promotions: "fixed price promotions", + flex_promotions: "flex promotions", + free_gift_promotions: "free gift promotions", + free_shipping_promotions: "free shipping promotions", + geocoders: "geocoders", + gift_card_recipients: "gift card recipients", + gift_cards: "gift cards", + google_geocoders: "google geocoders", + imports: "imports", + in_stock_subscriptions: "in stock subscriptions", + inventory_models: "inventory models", + inventory_return_locations: "inventory return locations", + inventory_stock_locations: "inventory stock locations", + klarna_gateways: "klarna gateways", + klarna_payments: "klarna payments", + line_item_options: "line item options", + line_items: "line items", + links: "links", + manual_gateways: "manual gateways", + manual_tax_calculators: "manual tax calculators", + markets: "markets", + merchants: "merchants", + notifications: "notifications", + order_amount_promotion_rules: "order amount promotion rules", + order_copies: "order copies", + order_factories: "order factories", + order_subscription_items: "order subscription items", + order_subscriptions: "order subscriptions", + orders: "orders", + packages: "packages", + parcel_line_items: "parcel line items", + parcels: "parcels", + payment_gateways: "payment gateways", + payment_methods: "payment methods", + payment_options: "payment options", + paypal_gateways: "paypal gateways", + paypal_payments: "paypal payments", + percentage_discount_promotions: "percentage discount promotions", + pickups: "pickups", + price_frequency_tiers: "price frequency tiers", + price_list_schedulers: "price list schedulers", + price_lists: "price lists", + price_tiers: "price tiers", + price_volume_tiers: "price volume tiers", + prices: "prices", + promotion_rules: "promotion rules", + promotions: "promotions", + recurring_order_copies: "recurring order copies", + refunds: "refunds", + reserved_stocks: "reserved stocks", + resource_errors: "resource errors", + return_line_items: "return line items", + returns: "returns", + satispay_gateways: "satispay gateways", + satispay_payments: "satispay payments", + shipments: "shipments", + shipping_categories: "shipping categories", + shipping_method_tiers: "shipping method tiers", + shipping_methods: "shipping methods", + shipping_weight_tiers: "shipping weight tiers", + shipping_zones: "shipping zones", + sku_list_items: "SKU list items", + sku_list_promotion_rules: "SKU list promotion rules", + sku_lists: "SKU lists", + sku_options: "SKU options", + skus: "SKUs", + stock_items: "stock items", + stock_line_items: "stock line items", + stock_locations: "stock locations", + stock_reservations: "stock reservations", + stock_transfers: "stock transfers", + stores: "Stores", + stripe_gateways: "stripe gateways", + stripe_payments: "stripe payments", + stripe_tax_accounts: "stripe tax accounts", + subscription_models: "subscription models", + tags: "tags", + talon_one_accounts: "talon.one accounts", + tax_calculators: "tax calculators", + tax_categories: "tax categories", + tax_rules: "tax rules", + taxjar_accounts: "taxjar accounts", + transactions: "transactions", + versions: "versions", + vertex_accounts: "vertex accounts", + voids: "voids", + webhooks: "webhooks", + wire_transfers: "wire transfers", } /** @@ -275,13 +275,13 @@ const pluralLowercase: Record = { export function formatResourceName({ resource, count = 1, - format = 'lower' + format = "lower", }: { resource: ListableResourceType - count?: number | 'singular' | 'plural' - format?: 'lower' | 'title' + count?: number | "singular" | "plural" + format?: "lower" | "title" }): string { - const isSingular = count === 1 || count === 'singular' + const isSingular = count === 1 || count === "singular" const dictionary = isSingular ? singularLowercase : pluralLowercase const resourceName = dictionary[resource] @@ -289,7 +289,7 @@ export function formatResourceName({ return resource } - if (format === 'title') { + if (format === "title") { return capitalizeFirstLetter(resourceName) } @@ -306,8 +306,8 @@ export type TriggerAttribute = Extract< > export type ResourceEndpoint = - | Exclude - | ('organization' | 'application') + | Exclude + | ("organization" | "application") /** * Get the resource endpoint for a given resource type. @@ -316,11 +316,11 @@ export type ResourceEndpoint = * @example getResourceEndpoint('organizations') // 'organization' */ export function getResourceEndpoint( - resourceType: ResourceTypeLock + resourceType: ResourceTypeLock, ): ResourceEndpoint { - return resourceType === 'organizations' - ? 'organization' - : resourceType === 'applications' - ? 'application' + return resourceType === "organizations" + ? "organization" + : resourceType === "applications" + ? "application" : resourceType } diff --git a/packages/app-elements/src/helpers/tracking.ts b/packages/app-elements/src/helpers/tracking.ts index c9a77acd1..217b126e5 100644 --- a/packages/app-elements/src/helpers/tracking.ts +++ b/packages/app-elements/src/helpers/tracking.ts @@ -1,28 +1,28 @@ -import { type AvatarProps } from '#ui/atoms/Avatar' -import { type Parcel, type Shipment } from '@commercelayer/sdk' -import orderBy from 'lodash-es/orderBy' -import { type SetNonNullable } from 'type-fest' -import { z } from 'zod' +import type { Parcel, Shipment } from "@commercelayer/sdk" +import orderBy from "lodash-es/orderBy" +import type { SetNonNullable } from "type-fest" +import { z } from "zod" +import type { AvatarProps } from "#ui/atoms/Avatar" -export function getAvatarSrcFromRate(rate: Rate): AvatarProps['src'] { +export function getAvatarSrcFromRate(rate: Rate): AvatarProps["src"] { switch (rate.carrier) { - case 'DHLEcommerceAsia': - case 'DhlEcs': - case 'DHLExpress': - case 'DHLPaket': - case 'DHLSmartmail': - return 'carriers:dhl' - case 'FedEx': - case 'FedExCrossBorder': - case 'FedExMailview': - case 'FedexSmartPost': - return 'carriers:fedex' - case 'UPS': - case 'UPSIparcel': - case 'UPSMailInnovations': - return 'carriers:ups' + case "DHLEcommerceAsia": + case "DhlEcs": + case "DHLExpress": + case "DHLPaket": + case "DHLSmartmail": + return "carriers:dhl" + case "FedEx": + case "FedExCrossBorder": + case "FedExMailview": + case "FedexSmartPost": + return "carriers:fedex" + case "UPS": + case "UPSIparcel": + case "UPSMailInnovations": + return "carriers:ups" default: - return 'carriers:generic' + return "carriers:generic" } } @@ -31,21 +31,21 @@ export function getAvatarSrcFromRate(rate: Rate): AvatarProps['src'] { */ export function getParcelTrackingDetails(parcel?: Parcel): TrackingDetail[] { const details = parcelTrackingDetailsSchema.safeParse( - parcel?.tracking_details + parcel?.tracking_details, ) if (!details.success) { return [] } - return orderBy(details.data, ['datetime'], ['desc']) + return orderBy(details.data, ["datetime"], ["desc"]) } /** * Get latest tracking details from a parcel. */ export function getParcelTrackingDetail( - parcel?: Parcel + parcel?: Parcel, ): TrackingDetail | undefined { return getParcelTrackingDetails(parcel)[0] } @@ -96,7 +96,7 @@ export function hasBeenPurchased(shipment: Shipment): boolean { */ const trackingDetailSchema = z.object({ /** "TrackingDetail" */ - object: z.literal('TrackingDetail'), + object: z.literal("TrackingDetail"), /** Description of the scan event */ message: z.string().nullable(), /** Status of the package at the time of the scan event, possible values are "unknown", "pre_transit", "in_transit", "out_for_delivery", "delivered", "available_for_pickup", "return_to_sender", "failure", "cancelled" or "error" */ @@ -111,7 +111,7 @@ const trackingDetailSchema = z.object({ tracking_location: z .object({ /** "TrackingLocation" */ - object: z.literal('TrackingLocation'), + object: z.literal("TrackingLocation"), /** The city where the scan event occurred (if available) */ city: z.string().nullable(), /** The state where the scan event occurred (if available) */ @@ -119,13 +119,13 @@ const trackingDetailSchema = z.object({ /** The country where the scan event occurred (if available) */ country: z.string().nullable(), /** The postal code where the scan event occurred (if available) */ - zip: z.string().nullable() + zip: z.string().nullable(), }) .transform((loc) => - loc.city != null ? (loc as SetNonNullable) : null + loc.city != null ? (loc as SetNonNullable) : null, ), description: z.string().nullable(), - carrier_code: z.string().nullable() + carrier_code: z.string().nullable(), }) /** @@ -135,7 +135,7 @@ const rateSchema = z.object({ /** unique, begins with 'rate_' */ id: z.string(), /** "test" or "production" */ - mode: z.literal('test').or(z.literal('production')), + mode: z.literal("test").or(z.literal("production")), /** service level/name @docs https://www.easypost.com/docs/api#service-levels */ service: z.string(), /** name of carrier */ @@ -157,7 +157,7 @@ const rateSchema = z.object({ /** formatted date for delivery */ formatted_delivery_date: z.string().optional(), /** the actual formatted rate quote for this service */ - formatted_rate: z.string() + formatted_rate: z.string(), }) const parcelTrackingDetailsSchema = z.array(trackingDetailSchema) diff --git a/packages/app-elements/src/helpers/transactions.test.ts b/packages/app-elements/src/helpers/transactions.test.ts index 40aa407d7..bd7f5b4bf 100644 --- a/packages/app-elements/src/helpers/transactions.test.ts +++ b/packages/app-elements/src/helpers/transactions.test.ts @@ -1,86 +1,86 @@ -import type { Authorization, Capture } from '@commercelayer/sdk' -import { orderTransactionIsAnAsyncCapture } from './transactions' +import type { Authorization, Capture } from "@commercelayer/sdk" +import { orderTransactionIsAnAsyncCapture } from "./transactions" const capture: Capture = { - id: 'capture-id', - type: 'captures', - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', + id: "capture-id", + type: "captures", + created_at: "2023-01-01T00:00:00Z", + updated_at: "2023-01-01T00:00:00Z", succeeded: false, - message: '', - error_code: '', + message: "", + error_code: "", amount_cents: 1000, - currency_code: 'USD', + currency_code: "USD", amount_float: 10, - formatted_amount: '$10.00', - number: '123456' + formatted_amount: "$10.00", + number: "123456", } -describe('orderTransactionIsAnAsyncCapture', () => { +describe("orderTransactionIsAnAsyncCapture", () => { it("returns false if transaction type is not 'captures'", () => { const transaction: Authorization = { - type: 'authorizations', + type: "authorizations", succeeded: false, - message: '', - error_code: '', + message: "", + error_code: "", amount_cents: 1000, - currency_code: 'USD', - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - id: 'capture-id', + currency_code: "USD", + created_at: "2023-01-01T00:00:00Z", + updated_at: "2023-01-01T00:00:00Z", + id: "capture-id", amount_float: 10, - formatted_amount: '$10.00', - number: '123456' + formatted_amount: "$10.00", + number: "123456", } expect(orderTransactionIsAnAsyncCapture(transaction)).toBe(false) }) - it('returns false if transaction.succeeded is true', () => { + it("returns false if transaction.succeeded is true", () => { const transaction: Capture = { ...capture, succeeded: true, - message: '', - error_code: '' + message: "", + error_code: "", } expect(orderTransactionIsAnAsyncCapture(transaction)).toBe(false) }) - it('returns false if transaction.message is not empty', () => { + it("returns false if transaction.message is not empty", () => { const transaction: Capture = { ...capture, succeeded: false, - message: 'Some message', - error_code: '' + message: "Some message", + error_code: "", } expect(orderTransactionIsAnAsyncCapture(transaction)).toBe(false) }) - it('returns false if transaction.error_code is not empty', () => { + it("returns false if transaction.error_code is not empty", () => { const transaction: Capture = { ...capture, succeeded: false, - message: '', - error_code: 'ERR123' + message: "", + error_code: "ERR123", } expect(orderTransactionIsAnAsyncCapture(transaction)).toBe(false) }) - it('returns true for async pending capture (not succeeded, empty message and error_code)', () => { + it("returns true for async pending capture (not succeeded, empty message and error_code)", () => { const transaction: Capture = { ...capture, succeeded: false, - message: '', - error_code: '' + message: "", + error_code: "", } expect(orderTransactionIsAnAsyncCapture(transaction)).toBe(true) }) - it('returns true for async pending capture (not succeeded, undefined message and error_code)', () => { + it("returns true for async pending capture (not succeeded, undefined message and error_code)", () => { const transaction: Capture = { ...capture, succeeded: false, message: undefined, - error_code: undefined + error_code: undefined, } expect(orderTransactionIsAnAsyncCapture(transaction)).toBe(true) }) diff --git a/packages/app-elements/src/helpers/transactions.ts b/packages/app-elements/src/helpers/transactions.ts index d8290ac85..0a2817aac 100644 --- a/packages/app-elements/src/helpers/transactions.ts +++ b/packages/app-elements/src/helpers/transactions.ts @@ -1,5 +1,5 @@ -import type { Authorization, Capture, Refund, Void } from '@commercelayer/sdk' -import isEmpty from 'lodash-es/isEmpty' +import type { Authorization, Capture, Refund, Void } from "@commercelayer/sdk" +import isEmpty from "lodash-es/isEmpty" /** * Check if the transaction is an async capture @@ -7,10 +7,10 @@ import isEmpty from 'lodash-es/isEmpty' * that are not succeeded and have no message or error code. */ export function orderTransactionIsAnAsyncCapture( - transaction: Authorization | Void | Capture | Refund + transaction: Authorization | Void | Capture | Refund, ): boolean { return ( - transaction.type === 'captures' && + transaction.type === "captures" && !transaction.succeeded && isEmpty(transaction.message) && isEmpty(transaction.error_code) diff --git a/packages/app-elements/src/helpers/unitsOfWeight.test.ts b/packages/app-elements/src/helpers/unitsOfWeight.test.ts index 9bf851eb2..a8fd3119b 100644 --- a/packages/app-elements/src/helpers/unitsOfWeight.test.ts +++ b/packages/app-elements/src/helpers/unitsOfWeight.test.ts @@ -1,32 +1,32 @@ -import { getUnitOfWeightName, getUnitsOfWeightForSelect } from './unitsOfWeight' +import { getUnitOfWeightName, getUnitsOfWeightForSelect } from "./unitsOfWeight" -describe('getUnitsOfWeightForSelect', () => { - test('Should return the array of units of weight suitable for a select', () => { +describe("getUnitsOfWeightForSelect", () => { + test("Should return the array of units of weight suitable for a select", () => { expect(getUnitsOfWeightForSelect()).toStrictEqual([ { - value: 'gr', - label: 'Grams' + value: "gr", + label: "Grams", }, { - value: 'lb', - label: 'Pounds' + value: "lb", + label: "Pounds", }, { - value: 'oz', - label: 'Ounces' - } + value: "oz", + label: "Ounces", + }, ]) }) }) -describe('getUnitOfWeightName', () => { - test('Should return `Grams`', () => { - expect(getUnitOfWeightName('gr')).toBe('Grams') +describe("getUnitOfWeightName", () => { + test("Should return `Grams`", () => { + expect(getUnitOfWeightName("gr")).toBe("Grams") }) - test('Should return `Pounds`', () => { - expect(getUnitOfWeightName('lb')).toBe('Pounds') + test("Should return `Pounds`", () => { + expect(getUnitOfWeightName("lb")).toBe("Pounds") }) - test('Should return `Ounces`', () => { - expect(getUnitOfWeightName('oz')).toBe('Ounces') + test("Should return `Ounces`", () => { + expect(getUnitOfWeightName("oz")).toBe("Ounces") }) }) diff --git a/packages/app-elements/src/helpers/unitsOfWeight.ts b/packages/app-elements/src/helpers/unitsOfWeight.ts index 666588698..1739823ed 100644 --- a/packages/app-elements/src/helpers/unitsOfWeight.ts +++ b/packages/app-elements/src/helpers/unitsOfWeight.ts @@ -1,9 +1,9 @@ -const unitsOfWeight = ['gr', 'lb', 'oz'] as const +const unitsOfWeight = ["gr", "lb", "oz"] as const const unitsOfWeightNames = { - gr: 'Grams', - lb: 'Pounds', - oz: 'Ounces' + gr: "Grams", + lb: "Pounds", + oz: "Ounces", } as const export type UnitOfWeight = (typeof unitsOfWeight)[number] @@ -23,13 +23,13 @@ export const getUnitsOfWeightForSelect = (): UnitOfWeightForSelect[] => { return unitsOfWeight.map((unitOfWeight) => { return { value: unitOfWeight, - label: unitsOfWeightNames[unitOfWeight] + label: unitsOfWeightNames[unitOfWeight], } }) } export function getUnitOfWeightName( - unitOfWeight: UnitOfWeight + unitOfWeight: UnitOfWeight, ): UnitOfWeightLabel { return unitsOfWeightNames[unitOfWeight] } diff --git a/packages/app-elements/src/helpers/useAppLinking.ts b/packages/app-elements/src/helpers/useAppLinking.ts index bd740fcc5..35283a336 100644 --- a/packages/app-elements/src/helpers/useAppLinking.ts +++ b/packages/app-elements/src/helpers/useAppLinking.ts @@ -1,8 +1,8 @@ -import { useTokenProvider } from '#providers/TokenProvider' -import { type TokenProviderClAppSlug } from '#providers/TokenProvider/types' -import isEmpty from 'lodash-es/isEmpty' -import { useCallback } from 'react' -import { useLocation, useRouter, useSearch } from 'wouter' +import isEmpty from "lodash-es/isEmpty" +import { useCallback } from "react" +import { useLocation, useRouter, useSearch } from "wouter" +import { useTokenProvider } from "#providers/TokenProvider" +import type { TokenProviderClAppSlug } from "#providers/TokenProvider/types" type Layout = Record interface AppsConfig { @@ -12,7 +12,7 @@ interface AppsConfig { // TODO: replace empty config with fetched config from TokenProvider const config: { apps: AppsConfig } = { - apps: {} + apps: {}, } interface UseAppLinkingHook { @@ -26,7 +26,7 @@ interface UseAppLinkingHook { e: React.MouseEvent< HTMLAnchorElement | HTMLDivElement | HTMLButtonElement, MouseEvent - > + >, ) => void } | null @@ -45,13 +45,13 @@ interface UseAppLinkingHook { */ export function useAppLinking(): UseAppLinkingHook { const { - settings: { isInDashboard, appSlug: currentAppSlug } + settings: { isInDashboard, appSlug: currentAppSlug }, } = useTokenProvider() const { base } = useRouter() const [location, setLocation] = useLocation() const search = useSearch() - const navigateTo: UseAppLinkingHook['navigateTo'] = useCallback( + const navigateTo: UseAppLinkingHook["navigateTo"] = useCallback( ({ app, resourceId }) => { const path = resourceId != null ? `/list/${resourceId}` : `/list` @@ -62,9 +62,9 @@ export function useAppLinking(): UseAppLinkingHook { const handleOnClick = ( e: Parameters< - NonNullable>['onClick'] + NonNullable>["onClick"] >[0], - to: string + to: string, ): void => { if (e.ctrlKey || e.metaKey) { // allow to open link in a new tab with ctrl+click or cmd+click @@ -81,7 +81,7 @@ export function useAppLinking(): UseAppLinkingHook { destinationApp: app, resourceId, returnToApp: currentAppSlug as TokenProviderClAppSlug, - location: `${location}${!isEmpty(search) ? `?${search}` : ''}` + location: `${location}${!isEmpty(search) ? `?${search}` : ""}`, }) setLocation(to) } @@ -93,7 +93,7 @@ export function useAppLinking(): UseAppLinkingHook { href: `${base}${path}`, onClick: (e) => { handleOnClick(e, path) - } + }, } } @@ -114,9 +114,9 @@ export function useAppLinking(): UseAppLinkingHook { onClick: (event) => { handleOnClick( event, - `${isExternalUrl(customInstruction) ? '' : '~'}${customInstruction}${path}` + `${isExternalUrl(customInstruction) ? "" : "~"}${customInstruction}${path}`, ) - } + }, } } @@ -128,23 +128,23 @@ export function useAppLinking(): UseAppLinkingHook { href: `${newBase}${path}`, onClick: (e) => { handleOnClick(e, `~${newBase}${path}`) - } + }, } }, - [base, currentAppSlug, isInDashboard] + [base, currentAppSlug, isInDashboard], ) const goBack = useCallback( ({ currentResourceId, - defaultRelativePath + defaultRelativePath, }: { currentResourceId?: string defaultRelativePath: string }) => { const goBackItem = getGoBackItem({ destinationApp: currentAppSlug as TokenProviderClAppSlug, - resourceId: currentResourceId + resourceId: currentResourceId, }) if (goBackItem == null) { setLocation(defaultRelativePath) @@ -153,20 +153,20 @@ export function useAppLinking(): UseAppLinkingHook { setLocation( goBackItem.returnToApp === currentAppSlug ? goBackItem.location // is same app - : `~${base.replace(`/${currentAppSlug}`, `/${goBackItem.returnToApp}`)}${goBackItem.location ?? ''}` // is new router base + : `~${base.replace(`/${currentAppSlug}`, `/${goBackItem.returnToApp}`)}${goBackItem.location ?? ""}`, // is new router base ) }, - [base, currentAppSlug] + [base, currentAppSlug], ) return { navigateTo, - goBack + goBack, } } function isExternalUrl(url: string): boolean { - return url.startsWith('http://') || url.startsWith('https://') + return url.startsWith("http://") || url.startsWith("https://") } function clearConfigPath(path?: string | null): string | null { @@ -175,10 +175,10 @@ function clearConfigPath(path?: string | null): string | null { } // enforce leading slash - path = isExternalUrl(path) || path.startsWith('/') ? path : `/${path}` + path = isExternalUrl(path) || path.startsWith("/") ? path : `/${path}` // remove trailing slash - return path.endsWith('/') ? path.slice(0, -1) : path + return path.endsWith("/") ? path.slice(0, -1) : path } interface GoBackItem { @@ -193,14 +193,14 @@ function saveGoBackItem({ destinationApp, resourceId, returnToApp, - location + location, }: { destinationApp: TokenProviderClAppSlug resourceId?: string returnToApp: TokenProviderClAppSlug location: string }): void { - if (typeof window === 'undefined') { + if (typeof window === "undefined") { return } const itemKey = makePersistentKey({ destinationApp, resourceId }) @@ -209,19 +209,19 @@ function saveGoBackItem({ JSON.stringify({ version: currentVersion, returnToApp, - location - }) + location, + }), ) } function getGoBackItem({ destinationApp, - resourceId + resourceId, }: { destinationApp: TokenProviderClAppSlug resourceId?: string }): GoBackItem | null { - if (typeof window === 'undefined') { + if (typeof window === "undefined") { return null } const itemKey = makePersistentKey({ destinationApp, resourceId }) @@ -229,7 +229,7 @@ function getGoBackItem({ sessionStorage.removeItem(itemKey) try { - const item = JSON.parse(value ?? '{}') as GoBackItem + const item = JSON.parse(value ?? "{}") as GoBackItem if (item.version === currentVersion) { return item } else { @@ -242,10 +242,10 @@ function getGoBackItem({ function makePersistentKey({ destinationApp, - resourceId + resourceId, }: { destinationApp: TokenProviderClAppSlug resourceId?: string }): string { - return `cl.apps.nav.${destinationApp}_${resourceId ?? 'list'}` + return `cl.apps.nav.${destinationApp}_${resourceId ?? "list"}` } diff --git a/packages/app-elements/src/hooks/useClickAway.ts b/packages/app-elements/src/hooks/useClickAway.ts index e50987c60..5d751a88e 100644 --- a/packages/app-elements/src/hooks/useClickAway.ts +++ b/packages/app-elements/src/hooks/useClickAway.ts @@ -1,43 +1,38 @@ -import { useCallback, useEffect, useRef, type RefObject } from 'react' +import { type RefObject, useCallback, useEffect, useRef } from "react" export const useClickAway = ( - onClickAway?: () => void + onClickAway: () => void, ): RefObject | undefined => { - if (onClickAway != null) { - const ref = useRef(null) + const ref = useRef(null) - const escapeListener = useCallback((event: KeyboardEvent) => { - if (event.key === 'Escape') { - onClickAway() - } - }, []) - - const clickListener = useCallback( - (event: MouseEvent) => { - if ( - ref.current != null && - !ref?.current.contains(event.target as Node) - ) { - onClickAway() - } - }, - [ref.current] - ) + const escapeListener = useCallback((event: KeyboardEvent) => { + if (event.key === "Escape") { + onClickAway() + } + }, []) - useEffect(() => { - document.addEventListener('click', clickListener) - document.addEventListener('keyup', escapeListener) - return () => { - document.removeEventListener('click', clickListener) - document.removeEventListener('keyup', escapeListener) + const clickListener = useCallback( + (event: MouseEvent) => { + if (ref.current != null && !ref?.current.contains(event.target as Node)) { + onClickAway() } - }, []) + }, + [ref.current], + ) - if (ref.current == null) { - return undefined + useEffect(() => { + document.addEventListener("click", clickListener) + document.addEventListener("keyup", escapeListener) + return () => { + document.removeEventListener("click", clickListener) + document.removeEventListener("keyup", escapeListener) } + }, []) - // @ts-expect-error ref.current is never null - return ref + if (ref.current == null) { + return undefined } + + // @ts-expect-error ref.current is never null + return ref } diff --git a/packages/app-elements/src/hooks/useDelayShow.test.tsx b/packages/app-elements/src/hooks/useDelayShow.test.tsx index d40843e1a..a44be1354 100644 --- a/packages/app-elements/src/hooks/useDelayShow.test.tsx +++ b/packages/app-elements/src/hooks/useDelayShow.test.tsx @@ -1,6 +1,6 @@ -import { act, render } from '@testing-library/react' -import { type JSX, type ReactNode } from 'react' -import { useDelayShow } from './useDelayShow' +import { act, render } from "@testing-library/react" +import type { JSX, ReactNode } from "react" +import { useDelayShow } from "./useDelayShow" interface Props { delayMs: number @@ -11,13 +11,13 @@ function DelayShow({ delayMs = 1000, children }: Props): JSX.Element | null { const [show] = useDelayShow(delayMs) return ( -
+
{children}
) } -describe('useDelayShow', () => { +describe("useDelayShow", () => { beforeEach(() => { vi.useFakeTimers() }) @@ -26,20 +26,20 @@ describe('useDelayShow', () => { vi.useRealTimers() }) - test('Show set show equal to `true` after X delayMs', async () => { + test("Show set show equal to `true` after X delayMs", async () => { const { getByTestId } = render( - Hello, I am some delayed content + Hello, I am some delayed content, ) - const element = getByTestId('delay-show') + const element = getByTestId("delay-show") expect(element).toBeInTheDocument() - expect(element.style).toHaveProperty('opacity', '0') + expect(element.style).toHaveProperty("opacity", "0") await act(() => vi.advanceTimersByTime(499)) - expect(element.style).toHaveProperty('opacity', '0') + expect(element.style).toHaveProperty("opacity", "0") await act(() => vi.advanceTimersByTime(1)) - expect(element.style).toHaveProperty('opacity', '1') + expect(element.style).toHaveProperty("opacity", "1") }) }) diff --git a/packages/app-elements/src/hooks/useDelayShow.ts b/packages/app-elements/src/hooks/useDelayShow.ts index fff8335e9..d4d703142 100644 --- a/packages/app-elements/src/hooks/useDelayShow.ts +++ b/packages/app-elements/src/hooks/useDelayShow.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react' +import { useEffect, useRef, useState } from "react" export const useDelayShow = (delayMs = 500): readonly [boolean] => { const [show, setShow] = useState(delayMs === 0) diff --git a/packages/app-elements/src/hooks/useEditMetadataOverlay.tsx b/packages/app-elements/src/hooks/useEditMetadataOverlay.tsx index 0dea92eec..c6ec1160d 100644 --- a/packages/app-elements/src/hooks/useEditMetadataOverlay.tsx +++ b/packages/app-elements/src/hooks/useEditMetadataOverlay.tsx @@ -1,17 +1,17 @@ -import { useOverlay } from '#hooks/useOverlay' -import { useTranslation } from '#providers/I18NProvider' -import { PageLayout } from '#ui/composite/PageLayout' -import { type ResourceMetadataProps } from '#ui/resources/ResourceMetadata' -import { ResourceMetadataForm } from '#ui/resources/ResourceMetadata/ResourceMetadataForm' -import { type FC, useCallback } from 'react' +import { type FC, useCallback } from "react" +import { useOverlay } from "#hooks/useOverlay" +import { useTranslation } from "#providers/I18NProvider" +import { PageLayout } from "#ui/composite/PageLayout" +import type { ResourceMetadataProps } from "#ui/resources/ResourceMetadata" +import { ResourceMetadataForm } from "#ui/resources/ResourceMetadata/ResourceMetadataForm" export interface EditMetadataOverlayProps { /** * Optional title shown as first line in edit overlay heading */ title?: string - resourceId: ResourceMetadataProps['resourceId'] - resourceType: ResourceMetadataProps['resourceType'] + resourceId: ResourceMetadataProps["resourceId"] + resourceType: ResourceMetadataProps["resourceType"] } interface MetadataOverlayHook { @@ -24,20 +24,20 @@ export function useEditMetadataOverlay(): MetadataOverlayHook { const { t } = useTranslation() const OverlayComponent = useCallback>( - ({ title = 'Back', resourceId, resourceType }) => { + ({ title = "Back", resourceId, resourceType }) => { return ( - + { close() - } + }, }} > ) }, - [OverlayElement] + [OverlayElement], ) return { show: open, - Overlay: OverlayComponent + Overlay: OverlayComponent, } } diff --git a/packages/app-elements/src/hooks/useEditTagsOverlay.tsx b/packages/app-elements/src/hooks/useEditTagsOverlay.tsx index b46ace0d8..82d6c91a0 100644 --- a/packages/app-elements/src/hooks/useEditTagsOverlay.tsx +++ b/packages/app-elements/src/hooks/useEditTagsOverlay.tsx @@ -1,20 +1,20 @@ -import { navigateTo } from '#helpers/appsNavigation' -import { useOverlay } from '#hooks/useOverlay' -import { useCoreApi, useCoreSdkProvider } from '#providers/CoreSdkProvider' -import { useTranslation } from '#providers/I18NProvider' -import { useTokenProvider } from '#providers/TokenProvider' -import { Button } from '#ui/atoms/Button' -import { Text } from '#ui/atoms/Text' -import { PageLayout } from '#ui/composite/PageLayout' +import type { ListResponse, Tag } from "@commercelayer/sdk" +import isEmpty from "lodash-es/isEmpty" +import { useCallback, useState } from "react" +import { navigateTo } from "#helpers/appsNavigation" +import { useOverlay } from "#hooks/useOverlay" +import { useCoreApi, useCoreSdkProvider } from "#providers/CoreSdkProvider" +import { useTranslation } from "#providers/I18NProvider" +import { useTokenProvider } from "#providers/TokenProvider" +import { Button } from "#ui/atoms/Button" +import { Text } from "#ui/atoms/Text" +import { PageLayout } from "#ui/composite/PageLayout" import { InputSelect, type InputSelectValue, - isMultiValueSelected -} from '#ui/forms/InputSelect' -import { type ResourceTagsProps } from '#ui/resources/ResourceTags' -import { type ListResponse, type Tag } from '@commercelayer/sdk' -import isEmpty from 'lodash-es/isEmpty' -import { useCallback, useState } from 'react' + isMultiValueSelected, +} from "#ui/forms/InputSelect" +import type { ResourceTagsProps } from "#ui/resources/ResourceTags" export interface EditTagsOverlayProps { /** @@ -25,8 +25,8 @@ export interface EditTagsOverlayProps { * Optional setting to define if tags app management link is to be shown in edit overlay heading */ showManageAction?: boolean - resourceId: ResourceTagsProps['resourceId'] - resourceType: ResourceTagsProps['resourceType'] + resourceId: ResourceTagsProps["resourceId"] + resourceType: ResourceTagsProps["resourceType"] } interface TagsOverlayHook { @@ -38,8 +38,8 @@ export function useEditTagsOverlay(): TagsOverlayHook { const { Overlay: OverlayElement, open, - close - } = useOverlay({ queryParam: 'edit-tags' }) + close, + } = useOverlay({ queryParam: "edit-tags" }) const { settings } = useTokenProvider() const { t } = useTranslation() @@ -49,42 +49,42 @@ export function useEditTagsOverlay(): TagsOverlayHook { const navigateToTagsManagement = navigateTo({ destination: { - app: 'tags', - mode: settings.mode - } + app: "tags", + mode: settings.mode, + }, }) - const resourceName = t('resources.tags.name_other') + const resourceName = t("resources.tags.name_other") return { show: open, Overlay: ({ - title = 'Back', + title = "Back", showManageAction = false, resourceId, - resourceType + resourceType, }) => { const { sdkClient } = useCoreSdkProvider() const { data: organization, isLoading: isOrganizationLoading } = - useCoreApi('organization', 'retrieve', []) + useCoreApi("organization", "retrieve", []) const { data: resourceTags, isLoading, - mutate: mutateResourceTags + mutate: mutateResourceTags, } = useCoreApi( resourceType, - 'tags', + "tags", resourceId == null || isEmpty(resourceId) ? null : [ resourceId, { - fields: ['id', 'name'], - pageSize: 25 - } - ] + fields: ["id", "name"], + pageSize: 25, + }, + ], ) const tagsToSelectOptions = useCallback( @@ -92,9 +92,9 @@ export function useEditTagsOverlay(): TagsOverlayHook { tags.map((item) => ({ value: item.id, label: `${item.name}`, - meta: item + meta: item, })), - [] + [], ) const selectedOptionsToTags = useCallback( @@ -103,17 +103,17 @@ export function useEditTagsOverlay(): TagsOverlayHook { return selectedOptions.map((item) => item.meta as Tag) } // We need to set this particular empty value because at the moment SDK expects always at least an empty tag object while updating the relationship - return [{ id: null, type: 'tags' } as unknown as Tag] + return [{ id: null, type: "tags" } as unknown as Tag] }, - [] + [], ) const [selectedTags, setSelectedTags] = useState( - tagsToSelectOptions(resourceTags ?? []) + tagsToSelectOptions(resourceTags ?? []), ) if (isLoading || isOrganizationLoading || resourceTags == null) { - return <> + return null } const maxAllowedTags = organization?.tags_max_allowed_number ?? 10 @@ -122,80 +122,80 @@ export function useEditTagsOverlay(): TagsOverlayHook { { void sdkClient[resourceType] .update( { id: resourceId, - tags: selectedOptionsToTags(selectedTags) + tags: selectedOptionsToTags(selectedTags), }, { - include: ['tags'] - } + include: ["tags"], + }, ) .then((updatedResource) => { const newTags = updatedResource.tags ?? [] void mutateResourceTags(newTags as ListResponse, { - revalidate: false + revalidate: false, }).then(() => { close() }) }) }} > - {t('common.update')} + {t("common.update")} } > { close() - } + }, }} toolbar={{ buttons: showManageAction != null && showManageAction ? [ { - label: t('common.manage_resource', { - resource: resourceName.toLowerCase() + label: t("common.manage_resource", { + resource: resourceName.toLowerCase(), }), - variant: 'secondary', - size: 'small', - onClick: navigateToTagsManagement?.onClick - } + variant: "secondary", + size: "small", + onClick: navigateToTagsManagement?.onClick, + }, ] - : [] + : [], }} > - {t('common.add_up_to', { + {t("common.add_up_to", { limit: maxAllowedTags, - resource: resourceName.toLowerCase() + resource: resourceName.toLowerCase(), })} {selectedTagsLimitReached && ( <> - {' '} - - {t('common.limit_reached')} + {" "} + + {t("common.limit_reached")} . )} - ) + ), }} isMulti isSearchable @@ -205,11 +205,11 @@ export function useEditTagsOverlay(): TagsOverlayHook { if (hint.length > 0) { return await sdkClient.tags .list({ - fields: ['id', 'name'], + fields: ["id", "name"], filters: { - ...(!isEmpty(hint) && { name_cont: hint }) + ...(!isEmpty(hint) && { name_cont: hint }), }, - pageSize: 25 + pageSize: 25, }) .then(tagsToSelectOptions) } @@ -220,7 +220,7 @@ export function useEditTagsOverlay(): TagsOverlayHook { onSelect={(selectedTags) => { if (isMultiValueSelected(selectedTags)) { setSelectedTagsLimitReached( - selectedTags.length >= maxAllowedTags + selectedTags.length >= maxAllowedTags, ) setSelectedTags(selectedTags) return @@ -231,6 +231,6 @@ export function useEditTagsOverlay(): TagsOverlayHook { ) - } + }, } } diff --git a/packages/app-elements/src/hooks/useIsChanged.test.tsx b/packages/app-elements/src/hooks/useIsChanged.test.tsx index 8175875f5..245281aee 100644 --- a/packages/app-elements/src/hooks/useIsChanged.test.tsx +++ b/packages/app-elements/src/hooks/useIsChanged.test.tsx @@ -1,48 +1,48 @@ -import { act, renderHook } from '@testing-library/react' -import { useIsChanged } from './useIsChanged' +import { act, renderHook } from "@testing-library/react" +import { useIsChanged } from "./useIsChanged" -describe('useIsChanged', () => { - test('Should detect changes of value', () => { - let value: Record = { foo: 'bar' } +describe("useIsChanged", () => { + test("Should detect changes of value", () => { + let value: Record = { foo: "bar" } const { result, rerender } = renderHook(() => useIsChanged({ - value - }) + value, + }), ) // changing value act(() => { - value = { foo: 'baz' } + value = { foo: "baz" } rerender() }) expect(result.current).toBe(true) // re-rendering with the same value act(() => { - value = { foo: 'baz' } + value = { foo: "baz" } rerender() }) expect(result.current).toBe(false) }) - test('Should trigger onChange callback every time value is changed', () => { + test("Should trigger onChange callback every time value is changed", () => { const mockedConsoleLog = vi - .spyOn(console, 'log') + .spyOn(console, "log") .mockImplementation(() => {}) - let value: Record = { foo: 'bar' } + let value: Record = { foo: "bar" } const { result, rerender } = renderHook(() => useIsChanged({ value, onChange: () => { - console.log('value is changed') - } - }) + console.log("value is changed") + }, + }), ) // changing value act(() => { - value = { foo: 'baz' } + value = { foo: "baz" } rerender() }) expect(result.current).toBe(true) @@ -50,7 +50,7 @@ describe('useIsChanged', () => { // re-rendering with the same value act(() => { - value = { foo: 'baz' } + value = { foo: "baz" } rerender() }) expect(result.current).toBe(false) @@ -58,7 +58,7 @@ describe('useIsChanged', () => { // changing value again act(() => { - value = { foo: 'baz', bar: 'foo' } + value = { foo: "baz", bar: "foo" } rerender() }) expect(result.current).toBe(true) diff --git a/packages/app-elements/src/hooks/useIsChanged.ts b/packages/app-elements/src/hooks/useIsChanged.ts index de620553a..8a8f6b0e2 100644 --- a/packages/app-elements/src/hooks/useIsChanged.ts +++ b/packages/app-elements/src/hooks/useIsChanged.ts @@ -1,5 +1,5 @@ -import isEqual from 'lodash-es/isEqual' -import { useEffect, useRef } from 'react' +import isEqual from "lodash-es/isEqual" +import { useEffect, useRef } from "react" /** * This hook is used to detect when a value has changed during new rendering. @@ -10,7 +10,7 @@ import { useEffect, useRef } from 'react' */ export function useIsChanged({ value, - onChange + onChange, }: { value: T onChange?: () => void diff --git a/packages/app-elements/src/hooks/useOnBlurFromContainer.ts b/packages/app-elements/src/hooks/useOnBlurFromContainer.ts index 8606c5d6c..1c109224f 100644 --- a/packages/app-elements/src/hooks/useOnBlurFromContainer.ts +++ b/packages/app-elements/src/hooks/useOnBlurFromContainer.ts @@ -1,4 +1,4 @@ -import { useCallback } from 'react' +import { useCallback } from "react" type OnBlur = (event: React.FocusEvent) => void @@ -18,6 +18,6 @@ export const useOnBlurFromContainer = (onBlur: () => void): OnBlur => { } }) }, - [onBlur] + [onBlur], ) } diff --git a/packages/app-elements/src/hooks/useOverlay.test.tsx b/packages/app-elements/src/hooks/useOverlay.test.tsx index 35833ae36..1a487d7f4 100644 --- a/packages/app-elements/src/hooks/useOverlay.test.tsx +++ b/packages/app-elements/src/hooks/useOverlay.test.tsx @@ -1,15 +1,16 @@ -import { act, fireEvent, render } from '@testing-library/react' -import { type JSX } from 'react' -import { useOverlay } from './useOverlay' +import { act, fireEvent, render } from "@testing-library/react" +import type { JSX } from "react" +import { useOverlay } from "./useOverlay" function OverlayScreen({ queryParam }: { queryParam?: string }): JSX.Element { const { Overlay, close, open } = useOverlay( - queryParam != null ? { queryParam } : undefined + queryParam != null ? { queryParam } : undefined, ) return (
)} -
+ , ) const element = utils.getByTestId(id) return { element, - ...utils + ...utils, } } -describe('TokenProvider', () => { +describe("TokenProvider", () => { const { location } = window beforeEach(() => { localStorage.clear() @@ -59,170 +59,170 @@ describe('TokenProvider', () => { delete (window as any).location ;(window as any).location = { ...location, - hostname: '' + hostname: "", } }) afterAll(function resetLocation() { ;(window as typeof globalThis).location = location }) - test('Should read token from props (kind: imports)', async () => { - vi.useFakeTimers({ toFake: ['Date'] }).setSystemTime(validDateNow) - window.location.hostname = 'giuseppe.commercelayer.app' + test("Should read token from props (kind: imports)", async () => { + vi.useFakeTimers({ toFake: ["Date"] }).setSystemTime(validDateNow) + window.location.hostname = "giuseppe.commercelayer.app" const onInvalidAuth = vi.fn() const { element, getByText } = setup({ - id: 'token-provider', - kind: 'imports', - appSlug: 'imports', + id: "token-provider", + kind: "imports", + appSlug: "imports", devMode: false, accessToken: accessTokenKindImports, onInvalidAuth, - loadingElement:
Loading...
+ loadingElement:
Loading...
, }) expect(element).toBeVisible() - expect(getByText('Loading...')).toBeVisible() + expect(getByText("Loading...")).toBeVisible() await waitFor(() => { - expect(getByText('content')).toBeVisible() + expect(getByText("content")).toBeVisible() }) - expect(getByText('mode: test')).toBeVisible() - expect(getByText('timezone: Europe/Rome')).toBeVisible() - expect(getByText('email: user@commercelayer.io')).toBeVisible() - expect(getByText('firstName: Ringo')).toBeVisible() - expect(getByText('lastName: Starr')).toBeVisible() - expect(getByText('displayName: R. Starr')).toBeVisible() - expect(getByText('fullName: Ringo Starr')).toBeVisible() + expect(getByText("mode: test")).toBeVisible() + expect(getByText("timezone: Europe/Rome")).toBeVisible() + expect(getByText("email: user@commercelayer.io")).toBeVisible() + expect(getByText("firstName: Ringo")).toBeVisible() + expect(getByText("lastName: Starr")).toBeVisible() + expect(getByText("displayName: R. Starr")).toBeVisible() + expect(getByText("fullName: Ringo Starr")).toBeVisible() - expect(getByText('can access orders: yes')).toBeVisible() - expect(getByText('can access exports: no')).toBeVisible() - expect(getByText('can access imports: yes')).toBeVisible() + expect(getByText("can access orders: yes")).toBeVisible() + expect(getByText("can access exports: no")).toBeVisible() + expect(getByText("can access imports: yes")).toBeVisible() expect(onInvalidAuth).toHaveBeenCalledTimes(0) }) - test('Should read token from props (kind: integration)', async () => { - vi.useFakeTimers({ toFake: ['Date'] }).setSystemTime(validDateNow) - window.location.hostname = 'giuseppe.commercelayer.app' + test("Should read token from props (kind: integration)", async () => { + vi.useFakeTimers({ toFake: ["Date"] }).setSystemTime(validDateNow) + window.location.hostname = "giuseppe.commercelayer.app" const onInvalidAuth = vi.fn() const { element, getByText } = setup({ - id: 'token-provider', - kind: 'integration', - appSlug: 'imports', + id: "token-provider", + kind: "integration", + appSlug: "imports", devMode: false, accessToken: accessTokenKindIntegration, onInvalidAuth, - loadingElement:
Loading...
+ loadingElement:
Loading...
, }) expect(element).toBeVisible() - expect(getByText('Loading...')).toBeVisible() + expect(getByText("Loading...")).toBeVisible() await waitFor(() => { - expect(getByText('content')).toBeVisible() + expect(getByText("content")).toBeVisible() }) - expect(getByText('mode: test')).toBeVisible() - expect(getByText('can access orders: yes')).toBeVisible() - expect(getByText('can access exports: yes')).toBeVisible() - expect(getByText('can access imports: yes')).toBeVisible() + expect(getByText("mode: test")).toBeVisible() + expect(getByText("can access orders: yes")).toBeVisible() + expect(getByText("can access exports: yes")).toBeVisible() + expect(getByText("can access imports: yes")).toBeVisible() expect(onInvalidAuth).toHaveBeenCalledTimes(0) }) - test('Should return live mode if token comes from live environment', async () => { - vi.useFakeTimers({ toFake: ['Date'] }).setSystemTime(validDateNow) - window.location.hostname = 'giuseppe.commercelayer.app' + test("Should return live mode if token comes from live environment", async () => { + vi.useFakeTimers({ toFake: ["Date"] }).setSystemTime(validDateNow) + window.location.hostname = "giuseppe.commercelayer.app" const onInvalidAuth = vi.fn() const { getByText } = setup({ - id: 'token-provider', - kind: 'imports', - appSlug: 'imports', + id: "token-provider", + kind: "imports", + appSlug: "imports", devMode: false, accessToken: accessTokenLive, - onInvalidAuth + onInvalidAuth, }) await waitFor(() => { - expect(getByText('mode: live')).toBeVisible() + expect(getByText("mode: live")).toBeVisible() }) }) - test('Should read token from url', async () => { - vi.useFakeTimers({ toFake: ['Date'] }).setSystemTime(validDateNow) - window.location.hostname = 'giuseppe.commercelayer.app' + test("Should read token from url", async () => { + vi.useFakeTimers({ toFake: ["Date"] }).setSystemTime(validDateNow) + window.location.hostname = "giuseppe.commercelayer.app" window.location.search = `?accessToken=${accessTokenKindImports}` const onInvalidAuth = vi.fn() const { element, getByText } = setup({ - id: 'token-provider', - kind: 'imports', - appSlug: 'imports', + id: "token-provider", + kind: "imports", + appSlug: "imports", devMode: false, onInvalidAuth, - loadingElement:
Loading...
+ loadingElement:
Loading...
, }) expect(element).toBeVisible() - expect(getByText('Loading...')).toBeVisible() + expect(getByText("Loading...")).toBeVisible() await waitFor(() => { - expect(getByText('content')).toBeVisible() + expect(getByText("content")).toBeVisible() }) expect(onInvalidAuth).toBeCalledTimes(0) }) - test('Should trigger expired token', async () => { + test("Should trigger expired token", async () => { // faking expired date - vi.useFakeTimers({ toFake: ['Date'] }).setSystemTime(expiredDateNow) + vi.useFakeTimers({ toFake: ["Date"] }).setSystemTime(expiredDateNow) const onInvalidAuth = vi.fn() const { getByText } = setup({ - id: 'token-provider', - kind: 'imports', - appSlug: 'imports', + id: "token-provider", + kind: "imports", + appSlug: "imports", devMode: false, accessToken: accessTokenKindImports, - onInvalidAuth + onInvalidAuth, }) await waitFor(() => { - expect(getByText('Invalid token')).toBeVisible() + expect(getByText("Invalid token")).toBeVisible() }) expect(onInvalidAuth).toHaveBeenCalledWith({ - dashboardUrl: 'https://dashboard.commercelayer.io/test/giuseppe', - reason: 'accessToken is expired' + dashboardUrl: "https://dashboard.commercelayer.io/test/giuseppe", + reason: "accessToken is expired", }) }) - test('Should trigger invalid when `kind` is not matched', async () => { - vi.useFakeTimers({ toFake: ['Date'] }).setSystemTime(validDateNow) + test("Should trigger invalid when `kind` is not matched", async () => { + vi.useFakeTimers({ toFake: ["Date"] }).setSystemTime(validDateNow) const onInvalidAuth = vi.fn() - vi.spyOn(console, 'error').mockImplementation(() => {}) + vi.spyOn(console, "error").mockImplementation(() => {}) const { getByText } = setup({ - id: 'token-provider', - kind: 'sales_channel', - appSlug: 'imports', + id: "token-provider", + kind: "sales_channel", + appSlug: "imports", devMode: false, loadingElement:
fetching token info
, errorElement:
custom error element
, accessToken: accessTokenKindImports, - onInvalidAuth + onInvalidAuth, }) - expect(getByText('fetching token info')).toBeVisible() + expect(getByText("fetching token info")).toBeVisible() await waitFor(() => { - expect(getByText('custom error element')).toBeVisible() + expect(getByText("custom error element")).toBeVisible() }) expect(onInvalidAuth).toHaveBeenCalledWith({ - dashboardUrl: 'https://dashboard.commercelayer.io/test/giuseppe', - reason: 'accessToken is not valid' + dashboardUrl: "https://dashboard.commercelayer.io/test/giuseppe", + reason: "accessToken is not valid", }) }) - test('Should be able to receive an emitInvalidAuth event', async () => { - vi.useFakeTimers({ toFake: ['Date'] }).setSystemTime(validDateNow) + test("Should be able to receive an emitInvalidAuth event", async () => { + vi.useFakeTimers({ toFake: ["Date"] }).setSystemTime(validDateNow) const onInvalidAuth = vi.fn() const { getByTestId, getByText } = render( -
+
{
This is my app
+
, ) - expect(getByTestId('token-provider')).toBeVisible() - expect(getByText('Loading...')).toBeVisible() + expect(getByTestId("token-provider")).toBeVisible() + expect(getByText("Loading...")).toBeVisible() await waitFor(() => { - expect(getByTestId('btn-emit-error')).toBeVisible() + expect(getByTestId("btn-emit-error")).toBeVisible() }) - getByTestId('btn-emit-error').click() + getByTestId("btn-emit-error").click() await waitFor(() => { - expect(getByText('Invalid token')).toBeVisible() + expect(getByText("Invalid token")).toBeVisible() }) expect(onInvalidAuth).toHaveBeenCalledWith({ - dashboardUrl: 'https://dashboard.commercelayer.io/test/giuseppe', - reason: 'custom error trigger' + dashboardUrl: "https://dashboard.commercelayer.io/test/giuseppe", + reason: "custom error trigger", }) }) }) -describe('TokenProvider and localStorage', () => { - test('Should save persistent token', async () => { - vi.useFakeTimers({ toFake: ['Date'] }).setSystemTime(validDateNow) +describe("TokenProvider and localStorage", () => { + test("Should save persistent token", async () => { + vi.useFakeTimers({ toFake: ["Date"] }).setSystemTime(validDateNow) const { getByText } = setup({ - id: 'token-provider', - kind: 'imports', - appSlug: 'imports', + id: "token-provider", + kind: "imports", + appSlug: "imports", devMode: true, accessToken: accessTokenKindImports, - onInvalidAuth: () => {} + onInvalidAuth: () => {}, }) await waitFor(() => { - expect(getByText('content')).toBeVisible() + expect(getByText("content")).toBeVisible() }) }) - test('Should read persistent token', async () => { - vi.useFakeTimers({ toFake: ['Date'] }).setSystemTime(validDateNow) + test("Should read persistent token", async () => { + vi.useFakeTimers({ toFake: ["Date"] }).setSystemTime(validDateNow) const { getByText } = setup({ - id: 'token-provider', - kind: 'imports', - appSlug: 'imports', + id: "token-provider", + kind: "imports", + appSlug: "imports", devMode: true, - onInvalidAuth: () => {} + onInvalidAuth: () => {}, }) await waitFor(() => { - expect(getByText('content')).toBeVisible() + expect(getByText("content")).toBeVisible() }) }) }) diff --git a/packages/app-elements/src/providers/TokenProvider/TokenProvider.tsx b/packages/app-elements/src/providers/TokenProvider/TokenProvider.tsx index 8742293de..c8d2a6339 100644 --- a/packages/app-elements/src/providers/TokenProvider/TokenProvider.tsx +++ b/packages/app-elements/src/providers/TokenProvider/TokenProvider.tsx @@ -1,46 +1,46 @@ -import { type TokenProviderTokenApplicationKind } from '#providers/TokenProvider' -import { - decodeExtras, - getExtrasFromUrl, - isValidUser -} from '#providers/TokenProvider/extras' -import { extractDomainFromApiBaseEndpoint } from '#providers/TokenProvider/url' -import { PageError } from '#ui/composite/PageError' -import { PageSkeleton } from '#ui/composite/PageSkeleton' -import { getCoreApiBaseEndpoint } from '@commercelayer/js-auth' -import type { ListableResourceType } from '@commercelayer/sdk' +import { getCoreApiBaseEndpoint } from "@commercelayer/js-auth" +import type { ListableResourceType } from "@commercelayer/sdk" import { createContext, + type ReactNode, useCallback, useContext, useEffect, useReducer, - type ReactNode -} from 'react' +} from "react" +import type { TokenProviderTokenApplicationKind } from "#providers/TokenProvider" +import { + decodeExtras, + getExtrasFromUrl, + isValidUser, +} from "#providers/TokenProvider/extras" +import { extractDomainFromApiBaseEndpoint } from "#providers/TokenProvider/url" +import { PageError } from "#ui/composite/PageError" +import { PageSkeleton } from "#ui/composite/PageSkeleton" import { getAccessTokenFromUrl, getCurrentMode, - removeAuthParamsFromUrl -} from './getAccessTokenFromUrl' -import { initialTokenProviderState, reducer } from './reducer' -import { getPersistentJWT, savePersistentJWT } from './storage' + removeAuthParamsFromUrl, +} from "./getAccessTokenFromUrl" +import { initialTokenProviderState, reducer } from "./reducer" +import { getPersistentJWT, savePersistentJWT } from "./storage" import type { TokenProviderAllowedAppSlug, TokenProviderAuthSettings, TokenProviderAuthUser, TokenProviderClAppSlug, TokenProviderExtras, - TokenProviderRoleActions -} from './types' -import { makeDashboardUrl } from './url' -import { isTokenExpired, isValidTokenForCurrentApp } from './validateToken' + TokenProviderRoleActions, +} from "./types" +import { makeDashboardUrl } from "./url" +import { isTokenExpired, isValidTokenForCurrentApp } from "./validateToken" export interface TokenProviderValue { settings: TokenProviderAuthSettings user: TokenProviderAuthUser | null canUser: ( action: TokenProviderRoleActions, - resource: ListableResourceType + resource: ListableResourceType, ) => boolean canAccess: (appSlug: TokenProviderClAppSlug) => boolean emitInvalidAuth: (reason: string) => void @@ -128,7 +128,7 @@ export const AuthContext = createContext({ canAccess: () => false, emitInvalidAuth: () => undefined, settings: initialTokenProviderState.settings, - user: null + user: null, }) export const useTokenProvider = (): TokenProviderValue => { @@ -148,7 +148,7 @@ export const TokenProvider: React.FC = ({ accessToken: accessTokenFromProp, onAppClose, isInDashboard = false, - extras: extrasFromProp + extras: extrasFromProp, }) => { const [_state, dispatch] = useReducer(reducer, initialTokenProviderState) const accessToken = @@ -159,14 +159,14 @@ export const TokenProvider: React.FC = ({ : getPersistentJWT({ appSlug, organizationSlug, - itemType: 'accessToken' + itemType: "accessToken", })) const encodeExtras = getExtrasFromUrl() ?? (storage?.getEncodedExtra != null ? storage?.getEncodedExtra() - : getPersistentJWT({ appSlug, organizationSlug, itemType: 'extras' })) + : getPersistentJWT({ appSlug, organizationSlug, itemType: "extras" })) const extras = extrasFromProp ?? decodeExtras(encodeExtras) @@ -176,60 +176,60 @@ export const TokenProvider: React.FC = ({ const dashboardUrl = makeDashboardUrl({ domain, - accessToken + accessToken, }) - const emitInvalidAuth = useCallback(function (reason: string): void { - dispatch({ type: 'invalidAuth' }) + const emitInvalidAuth = useCallback((reason: string): void => { + dispatch({ type: "invalidAuth" }) if (onInvalidAuth != null) { onInvalidAuth({ dashboardUrl, reason }) } }, []) const canUser = useCallback( - function ( + ( action: TokenProviderRoleActions, - resource: ListableResourceType | 'organizations' - ): boolean { - if (kind === 'integration') { + resource: ListableResourceType | "organizations", + ): boolean => { + if (kind === "integration") { return true } return Boolean(_state.rolePermissions?.[resource]?.[action]) }, - [_state.rolePermissions] + [_state.rolePermissions], ) const canAccess = useCallback( - function (appSlug: TokenProviderClAppSlug): boolean { - if (kind === 'integration') { + (appSlug: TokenProviderClAppSlug): boolean => { + if (kind === "integration") { return true } return _state.accessibleApps.includes(appSlug) }, - [_state.accessibleApps] + [_state.accessibleApps], ) useEffect( function validateAndSetToken() { void (async (): Promise => { if (apiBaseEndpoint == null) { - emitInvalidAuth('apiBaseEndpoint is missing') + emitInvalidAuth("apiBaseEndpoint is missing") return } if (accessToken == null) { - emitInvalidAuth('accessToken is missing') + emitInvalidAuth("accessToken is missing") return } if ( isTokenExpired({ accessToken, - compareTo: new Date() + compareTo: new Date(), }) ) { - emitInvalidAuth('accessToken is expired') + emitInvalidAuth("accessToken is expired") return } @@ -238,11 +238,11 @@ export const TokenProvider: React.FC = ({ kind, isProduction: !devMode, currentMode: getCurrentMode({ accessToken }), - organizationSlug + organizationSlug, }) if (!tokenInfo.isValidToken) { - emitInvalidAuth('accessToken is not valid') + emitInvalidAuth("accessToken is not valid") return } @@ -254,7 +254,7 @@ export const TokenProvider: React.FC = ({ appSlug, jwt: accessToken, organizationSlug, - itemType: 'accessToken' + itemType: "accessToken", }) } @@ -265,7 +265,7 @@ export const TokenProvider: React.FC = ({ appSlug, jwt: encodeExtras, organizationSlug, - itemType: 'extras' + itemType: "extras", }) } @@ -277,7 +277,7 @@ export const TokenProvider: React.FC = ({ : null dispatch({ - type: 'validToken', + type: "validToken", payload: { settings: { accessToken: tokenInfo.accessToken, @@ -289,16 +289,16 @@ export const TokenProvider: React.FC = ({ onAppClose, isInDashboard, scopes: tokenInfo.scopes, - extras + extras, }, user: tokenInfo.user ?? userFromExtras, rolePermissions: tokenInfo.permissions ?? {}, - accessibleApps: tokenInfo.accessibleApps ?? [] - } + accessibleApps: tokenInfo.accessibleApps ?? [], + }, }) })() }, - [accessToken] + [accessToken], ) const value: TokenProviderValue = { @@ -306,7 +306,7 @@ export const TokenProvider: React.FC = ({ user: _state.user, canUser, canAccess, - emitInvalidAuth + emitInvalidAuth, } if (_state.isTokenError) { @@ -314,9 +314,9 @@ export const TokenProvider: React.FC = ({ <> {errorElement ?? ( )} @@ -329,9 +329,9 @@ export const TokenProvider: React.FC = ({ return ( - {typeof children === 'function' ? children(value) : children} + {typeof children === "function" ? children(value) : children} ) } -TokenProvider.displayName = 'TokenProvider' +TokenProvider.displayName = "TokenProvider" diff --git a/packages/app-elements/src/providers/TokenProvider/extras.test.ts b/packages/app-elements/src/providers/TokenProvider/extras.test.ts index 1889a2bf9..c58f387a1 100644 --- a/packages/app-elements/src/providers/TokenProvider/extras.test.ts +++ b/packages/app-elements/src/providers/TokenProvider/extras.test.ts @@ -1,56 +1,56 @@ -import { isValidUser } from '#providers/TokenProvider/extras' -import { decodeExtras, encodeExtras, getExtrasFromUrl } from './extras' -import type { TokenProviderExtras } from './types' +import { isValidUser } from "#providers/TokenProvider/extras" +import { decodeExtras, encodeExtras, getExtrasFromUrl } from "./extras" +import type { TokenProviderExtras } from "./types" -describe('TokenProviderExtras encoding and decoding', () => { +describe("TokenProviderExtras encoding and decoding", () => { const extras: TokenProviderExtras = { salesChannels: [ - { name: 'Channel1', client_id: 'client1' }, - { name: 'Channel2', client_id: 'client2' } + { name: "Channel1", client_id: "client1" }, + { name: "Channel2", client_id: "client2" }, ], limits: { markets: 5, memberships: 10, organizations: 3, - skus: 1000 - } + skus: 1000, + }, } - test('should encode extras to a Base64 string', () => { + test("should encode extras to a Base64 string", () => { expect(encodeExtras(extras)).toBe( - 'eyJzYWxlc0NoYW5uZWxzIjpbeyJuYW1lIjoiQ2hhbm5lbDEiLCJjbGllbnRfaWQiOiJjbGllbnQxIn0seyJuYW1lIjoiQ2hhbm5lbDIiLCJjbGllbnRfaWQiOiJjbGllbnQyIn1dLCJsaW1pdHMiOnsibWFya2V0cyI6NSwibWVtYmVyc2hpcHMiOjEwLCJvcmdhbml6YXRpb25zIjozLCJza3VzIjoxMDAwfX0' + "eyJzYWxlc0NoYW5uZWxzIjpbeyJuYW1lIjoiQ2hhbm5lbDEiLCJjbGllbnRfaWQiOiJjbGllbnQxIn0seyJuYW1lIjoiQ2hhbm5lbDIiLCJjbGllbnRfaWQiOiJjbGllbnQyIn1dLCJsaW1pdHMiOnsibWFya2V0cyI6NSwibWVtYmVyc2hpcHMiOjEwLCJvcmdhbml6YXRpb25zIjozLCJza3VzIjoxMDAwfX0", ) }) - test('should decode a Base64 string back to the original extras object', () => { + test("should decode a Base64 string back to the original extras object", () => { const encoded = encodeExtras(extras) const decoded = decodeExtras(encoded) expect(decoded).toEqual(extras) }) - test('should handle empty extras object', () => { + test("should handle empty extras object", () => { const encoded = encodeExtras({}) const decoded = decodeExtras(encoded) expect(decoded).toEqual({}) }) - test('should handle extras with only salesChannels', () => { + test("should handle extras with only salesChannels", () => { const salesChannelsOnly: TokenProviderExtras = { - salesChannels: [{ name: 'Channel1', client_id: 'client1' }] + salesChannels: [{ name: "Channel1", client_id: "client1" }], } const encoded = encodeExtras(salesChannelsOnly) const decoded = decodeExtras(encoded) expect(decoded).toEqual(salesChannelsOnly) }) - test('should handle extras with only limits', () => { + test("should handle extras with only limits", () => { const limitsOnly: TokenProviderExtras = { limits: { markets: 5, memberships: 10, organizations: 3, - skus: 1000 - } + skus: 1000, + }, } const encoded = encodeExtras(limitsOnly) const decoded = decodeExtras(encoded) @@ -58,52 +58,52 @@ describe('TokenProviderExtras encoding and decoding', () => { }) }) -describe('getExtrasFromUrl', () => { +describe("getExtrasFromUrl", () => { const { location } = window beforeAll(function clearLocation() { delete (window as any).location ;(window as any).location = { ...location, - href: 'http://domain.com', - search: '' + href: "http://domain.com", + search: "", } }) afterAll(function resetLocation() { ;(window as typeof globalThis).location = location }) - test('accessToken exists in URL params', () => { - window.location.search = '?extras=eyJzYWxlc0NoYW5uZWxzIjpbeyJuYW1lIjoiQ' - expect(getExtrasFromUrl()).toBe('eyJzYWxlc0NoYW5uZWxzIjpbeyJuYW1lIjoiQ') + test("accessToken exists in URL params", () => { + window.location.search = "?extras=eyJzYWxlc0NoYW5uZWxzIjpbeyJuYW1lIjoiQ" + expect(getExtrasFromUrl()).toBe("eyJzYWxlc0NoYW5uZWxzIjpbeyJuYW1lIjoiQ") }) }) -describe('Encode object > Add in URL query string > Decode it from URL', () => { +describe("Encode object > Add in URL query string > Decode it from URL", () => { const { location } = window beforeAll(function clearLocation() { delete (window as any).location ;(window as any).location = { ...location, - href: 'http://domain.com', - search: '' + href: "http://domain.com", + search: "", } }) afterAll(function resetLocation() { ;(window as typeof globalThis).location = location }) - test('extras exists in URL params', () => { + test("extras exists in URL params", () => { const extras: TokenProviderExtras = { salesChannels: [ - { name: 'Channel1', client_id: 'client1' }, - { name: 'Channel2', client_id: 'client2' } + { name: "Channel1", client_id: "client1" }, + { name: "Channel2", client_id: "client2" }, ], limits: { markets: 5, memberships: 10, organizations: 3, - skus: 1000 - } + skus: 1000, + }, } const encoded = encodeExtras(extras) @@ -112,48 +112,48 @@ describe('Encode object > Add in URL query string > Decode it from URL', () => { expect(decoded).toEqual(extras) }) - test('extras does not exists in URL params', () => { - window.location.search = '?foo=bar' + test("extras does not exists in URL params", () => { + window.location.search = "?foo=bar" expect(decodeExtras(getExtrasFromUrl())).toEqual(undefined) }) }) -describe('isValidUser', () => { - test('should return true if user is valid', () => { +describe("isValidUser", () => { + test("should return true if user is valid", () => { const user = { - id: '1', - email: 'john@doe.com', - displayName: 'J.Doe', - firstName: 'John', - lastName: 'Doe', - fullName: 'John Doe', - timezone: 'UTC', - locale: 'en-US' + id: "1", + email: "john@doe.com", + displayName: "J.Doe", + firstName: "John", + lastName: "Doe", + fullName: "John Doe", + timezone: "UTC", + locale: "en-US", } as const expect(isValidUser(user)).toBe(true) }) - test('should return false if user is null', () => { + test("should return false if user is null", () => { expect(isValidUser(null)).toBe(false) }) - test('should return false if user is undefined', () => { + test("should return false if user is undefined", () => { expect(isValidUser(undefined)).toBe(false) }) - test('should return false if user is empty', () => { + test("should return false if user is empty", () => { // @ts-expect-error mismatching type for testing invalid user expect(isValidUser({})).toBe(false) }) - test('should return false if user is missing keys', () => { + test("should return false if user is missing keys", () => { const user = { - id: '1', - email: 'john@doe.com', - firstName: 'John', - lastName: 'Doe', - fullName: 'John Doe', - timezone: 'UTC' + id: "1", + email: "john@doe.com", + firstName: "John", + lastName: "Doe", + fullName: "John Doe", + timezone: "UTC", } // @ts-expect-error mismatching type for testing invalid user expect(isValidUser(user)).toBe(false) diff --git a/packages/app-elements/src/providers/TokenProvider/extras.ts b/packages/app-elements/src/providers/TokenProvider/extras.ts index 1d01dc4d7..be474f0fd 100644 --- a/packages/app-elements/src/providers/TokenProvider/extras.ts +++ b/packages/app-elements/src/providers/TokenProvider/extras.ts @@ -1,5 +1,5 @@ -import isEmpty from 'lodash-es/isEmpty' -import type { TokenProviderAuthUser, TokenProviderExtras } from './types' +import isEmpty from "lodash-es/isEmpty" +import type { TokenProviderAuthUser, TokenProviderExtras } from "./types" /** * Encodes the given extras object into a Base64 string. @@ -20,7 +20,7 @@ export function encodeExtras(extras: TokenProviderExtras): string { * @returns The decoded extras object. */ export function decodeExtras( - encodedExtras?: string | null + encodedExtras?: string | null, ): TokenProviderExtras | undefined { if (encodedExtras == null) { return undefined @@ -33,9 +33,9 @@ export function decodeExtras( * Try to get the extras value from the URL params. */ export const getExtrasFromUrl = (): string | undefined => { - if (typeof window !== 'undefined') { + if (typeof window !== "undefined") { const params = new URLSearchParams(window.location.search) - const extras = params.get('extras') + const extras = params.get("extras") return isEmpty(extras) || extras == null ? undefined : extras } } @@ -47,47 +47,47 @@ export const getExtrasFromUrl = (): string | undefined => { */ const base64URLSafe = { encode: (stringToEncode: string): string => { - if (typeof btoa !== 'undefined') { + if (typeof btoa !== "undefined") { return ( btoa(stringToEncode) // Remove padding equal characters - .replaceAll('=', '') + .replaceAll("=", "") // Replace characters according to base64url specifications - .replaceAll('+', '-') - .replaceAll('/', '_') + .replaceAll("+", "-") + .replaceAll("/", "_") ) } - return Buffer.from(stringToEncode, 'binary').toString('base64url') + return Buffer.from(stringToEncode, "binary").toString("base64url") }, decode: (encodedData: string): string => { - if (typeof atob !== 'undefined') { + if (typeof atob !== "undefined") { return atob( encodedData // Replace characters according to base64url specifications - .replaceAll('-', '+') - .replaceAll('_', '/') + .replaceAll("-", "+") + .replaceAll("_", "/"), ) } - return Buffer.from(encodedData, 'base64url').toString('binary') - } + return Buffer.from(encodedData, "base64url").toString("binary") + }, } /** * Validates if the user object received from `extras` is a valid one and can be added to the TokenProvider context. */ export function isValidUser( - user?: TokenProviderAuthUser | null + user?: TokenProviderAuthUser | null, ): user is TokenProviderAuthUser { const compareKeys = Object.keys({ - id: '', - email: '', - firstName: '', - lastName: '', - displayName: '', - fullName: '', - timezone: '', - locale: 'en-US' + id: "", + email: "", + firstName: "", + lastName: "", + displayName: "", + fullName: "", + timezone: "", + locale: "en-US", } satisfies TokenProviderAuthUser).sort() if (user == null || isEmpty(user)) { diff --git a/packages/app-elements/src/providers/TokenProvider/getAccessTokenFromUrl.test.ts b/packages/app-elements/src/providers/TokenProvider/getAccessTokenFromUrl.test.ts index 9ce94a9c8..f09b84478 100644 --- a/packages/app-elements/src/providers/TokenProvider/getAccessTokenFromUrl.test.ts +++ b/packages/app-elements/src/providers/TokenProvider/getAccessTokenFromUrl.test.ts @@ -1,46 +1,46 @@ -import { getAccessTokenFromUrl, getCurrentMode } from './getAccessTokenFromUrl' +import { getAccessTokenFromUrl, getCurrentMode } from "./getAccessTokenFromUrl" -describe('Read JWT from URL', () => { +describe("Read JWT from URL", () => { const { location } = window beforeAll(function clearLocation() { delete (window as any).location ;(window as any).location = { ...location, - href: 'http://domain.com', - search: '' + href: "http://domain.com", + search: "", } }) afterAll(function resetLocation() { ;(window as typeof globalThis).location = location }) - test('accessToken exists in URL params', () => { - window.location.search = '?accessToken=eyJhbGciOiJIUzUxMiJ9' - expect(getAccessTokenFromUrl()).toBe('eyJhbGciOiJIUzUxMiJ9') + test("accessToken exists in URL params", () => { + window.location.search = "?accessToken=eyJhbGciOiJIUzUxMiJ9" + expect(getAccessTokenFromUrl()).toBe("eyJhbGciOiJIUzUxMiJ9") }) - test('accessToken exists un URL along with other params', () => { + test("accessToken exists un URL along with other params", () => { window.location.search = - '?foo=bar&accessToken=eyJhbGciOiJIUzUxMiJ9&client=abc123' - expect(getAccessTokenFromUrl()).toBe('eyJhbGciOiJIUzUxMiJ9') + "?foo=bar&accessToken=eyJhbGciOiJIUzUxMiJ9&client=abc123" + expect(getAccessTokenFromUrl()).toBe("eyJhbGciOiJIUzUxMiJ9") }) - test('accessToken is empty', () => { - window.location.search = '?accessToken=' + test("accessToken is empty", () => { + window.location.search = "?accessToken=" expect(getAccessTokenFromUrl()).toBe(null) }) - test('Query string is empty', () => { - window.location.search = '' + test("Query string is empty", () => { + window.location.search = "" expect(getAccessTokenFromUrl()).toBe(null) }) }) -describe('getCurrentMode', () => { +describe("getCurrentMode", () => { const originalLocationObj = window.location beforeEach(() => { ;(window as typeof globalThis).location = { - ...originalLocationObj + ...originalLocationObj, } }) @@ -48,22 +48,22 @@ describe('getCurrentMode', () => { ;(window as typeof globalThis).location = originalLocationObj }) - test('should return the mode from the access token', () => { + test("should return the mode from the access token", () => { expect( getCurrentMode({ accessToken: - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJ0ZXN0Ijp0cnVlLCJleHAiOjE2NTI3OTUxMDIsInJhbmQiOjAuMzE0NTUwMDUwMTg4ODYzOH0.mX4A08-f_vdab6_dDpA1eDdGri91kR0erP8X7obZr1M' - }) - ).toBe('test') + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJ0ZXN0Ijp0cnVlLCJleHAiOjE2NTI3OTUxMDIsInJhbmQiOjAuMzE0NTUwMDUwMTg4ODYzOH0.mX4A08-f_vdab6_dDpA1eDdGri91kR0erP8X7obZr1M", + }), + ).toBe("test") }) - test('should return test mode from the URL params', () => { - window.location.search = '?mode=test' - expect(getCurrentMode({ accessToken: null })).toBe('test') + test("should return test mode from the URL params", () => { + window.location.search = "?mode=test" + expect(getCurrentMode({ accessToken: null })).toBe("test") }) - test('should return live mode from the URL params', () => { - window.location.search = '?mode=live' - expect(getCurrentMode({ accessToken: null })).toBe('live') + test("should return live mode from the URL params", () => { + window.location.search = "?mode=live" + expect(getCurrentMode({ accessToken: null })).toBe("live") }) }) diff --git a/packages/app-elements/src/providers/TokenProvider/getAccessTokenFromUrl.ts b/packages/app-elements/src/providers/TokenProvider/getAccessTokenFromUrl.ts index e7b25f67c..e577ca909 100644 --- a/packages/app-elements/src/providers/TokenProvider/getAccessTokenFromUrl.ts +++ b/packages/app-elements/src/providers/TokenProvider/getAccessTokenFromUrl.ts @@ -1,11 +1,11 @@ -import { type Mode } from '#providers/TokenProvider/types' -import isEmpty from 'lodash-es/isEmpty' -import { getInfoFromJwt } from './getInfoFromJwt' +import isEmpty from "lodash-es/isEmpty" +import type { Mode } from "#providers/TokenProvider/types" +import { getInfoFromJwt } from "./getInfoFromJwt" export const getAccessTokenFromUrl = (): string | null => { - if (typeof window !== 'undefined') { + if (typeof window !== "undefined") { const params = new URLSearchParams(window.location.search) - const accessToken = params.get('accessToken') + const accessToken = params.get("accessToken") return isEmpty(accessToken) ? null : accessToken } @@ -17,18 +17,18 @@ export const getAccessTokenFromUrl = (): string | null => { * If no mode is found, return 'live' as optimistic default. */ export const getCurrentMode = ({ - accessToken + accessToken, }: { accessToken?: string | null }): Mode => { - if (typeof window === 'undefined') { - return 'live' + if (typeof window === "undefined") { + return "live" } - const defaultMode = 'live' - const modeParam = new URLSearchParams(window.location.search).get('mode') + const defaultMode = "live" + const modeParam = new URLSearchParams(window.location.search).get("mode") - if (modeParam === 'test' || modeParam === 'live') { + if (modeParam === "test" || modeParam === "live") { return modeParam } @@ -41,11 +41,11 @@ export const getCurrentMode = ({ } export const removeAuthParamsFromUrl = (): void => { - if (typeof window !== 'undefined') { + if (typeof window !== "undefined") { const url = new URL(window.location.href) - url.searchParams.delete('accessToken') - url.searchParams.delete('extras') - url.searchParams.delete('mode') - window.history.replaceState({}, '', url.toString()) + url.searchParams.delete("accessToken") + url.searchParams.delete("extras") + url.searchParams.delete("mode") + window.history.replaceState({}, "", url.toString()) } } diff --git a/packages/app-elements/src/providers/TokenProvider/getInfoFromJwt.test.ts b/packages/app-elements/src/providers/TokenProvider/getInfoFromJwt.test.ts index 75c9c82fc..bb0e24e78 100644 --- a/packages/app-elements/src/providers/TokenProvider/getInfoFromJwt.test.ts +++ b/packages/app-elements/src/providers/TokenProvider/getInfoFromJwt.test.ts @@ -1,18 +1,18 @@ -import { getInfoFromJwt } from './getInfoFromJwt' +import { getInfoFromJwt } from "./getInfoFromJwt" const jwtSalesChannelOrgAcme = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJ0ZXN0Ijp0cnVlLCJleHAiOjE2NTI3OTUxMDIsInJhbmQiOjAuMzE0NTUwMDUwMTg4ODYzOH0.mX4A08-f_vdab6_dDpA1eDdGri91kR0erP8X7obZr1M' -describe('Get info from JWT', () => { - test('Parsing a valid token', () => { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJ0ZXN0Ijp0cnVlLCJleHAiOjE2NTI3OTUxMDIsInJhbmQiOjAuMzE0NTUwMDUwMTg4ODYzOH0.mX4A08-f_vdab6_dDpA1eDdGri91kR0erP8X7obZr1M" +describe("Get info from JWT", () => { + test("Parsing a valid token", () => { const { appKind, orgSlug, exp } = getInfoFromJwt(jwtSalesChannelOrgAcme) - expect(appKind).toBe('sales_channel') - expect(orgSlug).toBe('acme') + expect(appKind).toBe("sales_channel") + expect(orgSlug).toBe("acme") expect(exp).toBe(1652795102) }) - test('Parsing a malformed token', () => { + test("Parsing a malformed token", () => { const { appKind, orgSlug } = getInfoFromJwt( - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", ) expect(appKind).toBe(undefined) expect(orgSlug).toBe(undefined) @@ -21,82 +21,82 @@ describe('Get info from JWT', () => { // "scope": "" const tokenWithEmptyStringScope = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJzY29wZSI6IiIsInRlc3QiOnRydWUsImV4cCI6MTY1Mjc5NTEwMiwicmFuZCI6MC4zMTQ1NTAwNTAxODg4NjM4fQ.5VjX9oK2pkMHxZ-O4pSS2YyAAmfGWulpz_ii5ZvF0T0' + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJzY29wZSI6IiIsInRlc3QiOnRydWUsImV4cCI6MTY1Mjc5NTEwMiwicmFuZCI6MC4zMTQ1NTAwNTAxODg4NjM4fQ.5VjX9oK2pkMHxZ-O4pSS2YyAAmfGWulpz_ii5ZvF0T0" // "scope": "market:id:qaoeabcdoJ stock_location:id:zdMabcdVbb" const tokenWithScopeIds = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJzY29wZSI6Im1hcmtldDppZDpxYW9lYWJjZG9KIHN0b2NrX2xvY2F0aW9uOmlkOnpkTWFiY2RWYmIiLCJ0ZXN0Ijp0cnVlLCJleHAiOjE2NTI3OTUxMDIsInJhbmQiOjAuMzE0NTUwMDUwMTg4ODYzOH0.u_D63no4nbizAwQGUR4j6WTybAW_dP6OIV7gRokX60A' + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJzY29wZSI6Im1hcmtldDppZDpxYW9lYWJjZG9KIHN0b2NrX2xvY2F0aW9uOmlkOnpkTWFiY2RWYmIiLCJ0ZXN0Ijp0cnVlLCJleHAiOjE2NTI3OTUxMDIsInJhbmQiOjAuMzE0NTUwMDUwMTg4ODYzOH0.u_D63no4nbizAwQGUR4j6WTybAW_dP6OIV7gRokX60A" // "scope": "market:id:qaoeabcdoJ stock_location:id:zdMabcdVbb market:code:EUROPE stock_location:code:MAIN" const tokenWithScopeCodesAndIds = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJzY29wZSI6Im1hcmtldDppZDpxYW9lYWJjZG9KIHN0b2NrX2xvY2F0aW9uOmlkOnpkTWFiY2RWYmIgbWFya2V0OmNvZGU6RVVST1BFIHN0b2NrX2xvY2F0aW9uOmNvZGU6TUFJTiIsInRlc3QiOnRydWUsImV4cCI6MTY1Mjc5NTEwMiwicmFuZCI6MC4zMTQ1NTAwNTAxODg4NjM4fQ.q-Gub7C5IkmNNK9QjttXvDxNZhwHqxh7ocO9471Br0c' + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJzY29wZSI6Im1hcmtldDppZDpxYW9lYWJjZG9KIHN0b2NrX2xvY2F0aW9uOmlkOnpkTWFiY2RWYmIgbWFya2V0OmNvZGU6RVVST1BFIHN0b2NrX2xvY2F0aW9uOmNvZGU6TUFJTiIsInRlc3QiOnRydWUsImV4cCI6MTY1Mjc5NTEwMiwicmFuZCI6MC4zMTQ1NTAwNTAxODg4NjM4fQ.q-Gub7C5IkmNNK9QjttXvDxNZhwHqxh7ocO9471Br0c" // "scope": "market:id:qaoeabcdoJ market:id:qakufEcdoJ" const tokenWithScopeMultiMarketIds = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJzY29wZSI6Im1hcmtldDppZDpxYW9lYWJjZG9KIG1hcmtldDppZDpxYWt1ZkVjZG9KIiwidGVzdCI6dHJ1ZSwiZXhwIjoxNjUyNzk1MTAyLCJyYW5kIjowLjMxNDU1MDA1MDE4ODg2Mzh9.r_az60tlatjL5ZAsdKu0Hyxgw7Ijo_xMHE9vbbG0ZtU' + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJzY29wZSI6Im1hcmtldDppZDpxYW9lYWJjZG9KIG1hcmtldDppZDpxYWt1ZkVjZG9KIiwidGVzdCI6dHJ1ZSwiZXhwIjoxNjUyNzk1MTAyLCJyYW5kIjowLjMxNDU1MDA1MDE4ODg2Mzh9.r_az60tlatjL5ZAsdKu0Hyxgw7Ijo_xMHE9vbbG0ZtU" // "scope": "stock_location:code:EU1 stock_location:code:EU2" const tokenWithScopeMultiStockLocationCodes = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJzY29wZSI6InN0b2NrX2xvY2F0aW9uOmNvZGU6RVUxIHN0b2NrX2xvY2F0aW9uOmNvZGU6RVUyIiwidGVzdCI6dHJ1ZSwiZXhwIjoxNjUyNzk1MTAyLCJyYW5kIjowLjMxNDU1MDA1MDE4ODg2Mzh9.bmcfynHE5IZSqdRDqdN_PxZrZA7JkpS6H8034K0hKhc' + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJhYmMxMjM0Iiwic2x1ZyI6ImFjbWUifSwiYXBwbGljYXRpb24iOnsiaWQiOiJiY2Q0NDIxIiwia2luZCI6InNhbGVzX2NoYW5uZWwiLCJwdWJsaWMiOnRydWV9LCJzY29wZSI6InN0b2NrX2xvY2F0aW9uOmNvZGU6RVUxIHN0b2NrX2xvY2F0aW9uOmNvZGU6RVUyIiwidGVzdCI6dHJ1ZSwiZXhwIjoxNjUyNzk1MTAyLCJyYW5kIjowLjMxNDU1MDA1MDE4ODg2Mzh9.bmcfynHE5IZSqdRDqdN_PxZrZA7JkpS6H8034K0hKhc" -describe('Get scopes from token', () => { - test('should return empty values when token has no scope key', () => { +describe("Get scopes from token", () => { + test("should return empty values when token has no scope key", () => { const { scopes } = getInfoFromJwt(jwtSalesChannelOrgAcme) expect(scopes).toEqual({ market: {}, - stock_location: {} + stock_location: {}, }) }) - test('should return empty values when token has empty string as scope', () => { + test("should return empty values when token has empty string as scope", () => { const { scopes } = getInfoFromJwt(tokenWithEmptyStringScope) expect(scopes).toEqual({ market: {}, - stock_location: {} + stock_location: {}, }) }) - test('should return valid ids as scope', () => { + test("should return valid ids as scope", () => { const { scopes } = getInfoFromJwt(tokenWithScopeIds) expect(scopes).toEqual({ market: { - ids: ['qaoeabcdoJ'] + ids: ["qaoeabcdoJ"], }, stock_location: { - ids: ['zdMabcdVbb'] - } + ids: ["zdMabcdVbb"], + }, }) }) - test('should return valid ids and codes as scope', () => { + test("should return valid ids and codes as scope", () => { const { scopes } = getInfoFromJwt(tokenWithScopeCodesAndIds) expect(scopes).toEqual({ market: { - ids: ['qaoeabcdoJ'], - codes: ['EUROPE'] + ids: ["qaoeabcdoJ"], + codes: ["EUROPE"], }, stock_location: { - ids: ['zdMabcdVbb'], - codes: ['MAIN'] - } + ids: ["zdMabcdVbb"], + codes: ["MAIN"], + }, }) }) - test('should return valid ids when is multi markets', () => { + test("should return valid ids when is multi markets", () => { const { scopes } = getInfoFromJwt(tokenWithScopeMultiMarketIds) expect(scopes).toEqual({ market: { - ids: ['qaoeabcdoJ', 'qakufEcdoJ'] + ids: ["qaoeabcdoJ", "qakufEcdoJ"], }, - stock_location: {} + stock_location: {}, }) }) - test('should return valid codes when is multi stock locations', () => { + test("should return valid codes when is multi stock locations", () => { const { scopes } = getInfoFromJwt(tokenWithScopeMultiStockLocationCodes) expect(scopes).toEqual({ market: {}, stock_location: { - codes: ['EU1', 'EU2'] - } + codes: ["EU1", "EU2"], + }, }) }) }) diff --git a/packages/app-elements/src/providers/TokenProvider/getInfoFromJwt.ts b/packages/app-elements/src/providers/TokenProvider/getInfoFromJwt.ts index 2cc05eade..9645758fc 100644 --- a/packages/app-elements/src/providers/TokenProvider/getInfoFromJwt.ts +++ b/packages/app-elements/src/providers/TokenProvider/getInfoFromJwt.ts @@ -1,6 +1,6 @@ -import { jwtDecode } from 'jwt-decode' -import isEmpty from 'lodash-es/isEmpty' -import { type Mode } from './types' +import { jwtDecode } from "jwt-decode" +import isEmpty from "lodash-es/isEmpty" +import type { Mode } from "./types" interface JWTProps { organization: { @@ -17,7 +17,7 @@ interface JWTProps { } export const getInfoFromJwt = ( - accessToken: string + accessToken: string, ): { orgSlug?: string appKind?: string @@ -34,11 +34,11 @@ export const getInfoFromJwt = ( orgSlug: organization.slug, appKind: application.kind, appId: application.id, - mode: test ? 'test' : 'live', + mode: test ? "test" : "live", scopes: parseScope(scope), - exp + exp, } - } catch (e) { + } catch (_e) { return {} } } @@ -57,27 +57,27 @@ export interface ParsedScopes { const parseScope = (scope?: string): ParsedScopes => { const defaultData: ParsedScopes = { market: {}, - stock_location: {} + stock_location: {}, } if (isEmpty(scope) || scope == null) { return defaultData } - const scopeParts = scope.split(' ') + const scopeParts = scope.split(" ") const data = scopeParts.reduce((acc, part) => { - const [type, attribute, id] = part.split(':') + const [type, attribute, id] = part.split(":") - if (type !== 'market' && type !== 'stock_location') { + if (type !== "market" && type !== "stock_location") { return acc } const key: - | keyof ParsedScopes['market'] - | keyof ParsedScopes['stock_location'] + | keyof ParsedScopes["market"] + | keyof ParsedScopes["stock_location"] | null = - attribute === 'id' ? 'ids' : attribute === 'code' ? 'codes' : null + attribute === "id" ? "ids" : attribute === "code" ? "codes" : null if (key == null) { return acc } @@ -88,8 +88,8 @@ const parseScope = (scope?: string): ParsedScopes => { ...acc, [type]: { ...acc[type], - [key]: [...existingValues, id] - } + [key]: [...existingValues, id], + }, } }, defaultData) diff --git a/packages/app-elements/src/providers/TokenProvider/index.tsx b/packages/app-elements/src/providers/TokenProvider/index.tsx index fc31be854..655e01212 100644 --- a/packages/app-elements/src/providers/TokenProvider/index.tsx +++ b/packages/app-elements/src/providers/TokenProvider/index.tsx @@ -1,6 +1,6 @@ -export { encodeExtras } from './extras' -export { MetaTags } from './MetaTags' -export { TokenProvider, useTokenProvider } from './TokenProvider' +export { encodeExtras } from "./extras" +export { MetaTags } from "./MetaTags" +export { TokenProvider, useTokenProvider } from "./TokenProvider" export type { TokenProviderAllowedApp, TokenProviderAllowedAppKind, @@ -10,6 +10,6 @@ export type { TokenProviderPermissionItem, TokenProviderRoleActions, TokenProviderRolePermissions, - TokenProviderTokenApplicationKind -} from './types' + TokenProviderTokenApplicationKind, +} from "./types" // diff --git a/packages/app-elements/src/providers/TokenProvider/reducer.ts b/packages/app-elements/src/providers/TokenProvider/reducer.ts index 200699e82..a90079e9b 100644 --- a/packages/app-elements/src/providers/TokenProvider/reducer.ts +++ b/packages/app-elements/src/providers/TokenProvider/reducer.ts @@ -1,9 +1,9 @@ -import { type TokenProviderClAppSlug } from '#providers/TokenProvider' -import { - type TokenProviderAuthSettings, - type TokenProviderAuthUser, - type TokenProviderRolePermissions -} from './types' +import type { TokenProviderClAppSlug } from "#providers/TokenProvider" +import type { + TokenProviderAuthSettings, + TokenProviderAuthUser, + TokenProviderRolePermissions, +} from "./types" interface TokenProviderInternalState { validAuthToken?: string @@ -22,21 +22,21 @@ export const initialTokenProviderState: TokenProviderInternalState = { rolePermissions: {}, accessibleApps: [], settings: { - mode: 'test', - accessToken: '', - domain: 'commercelayer.io', - organizationSlug: '', - appSlug: '', + mode: "test", + accessToken: "", + domain: "commercelayer.io", + organizationSlug: "", + appSlug: "", isInDashboard: false, - dashboardUrl: 'https://dashboard.commercelayer.io/' + dashboardUrl: "https://dashboard.commercelayer.io/", }, - user: null + user: null, } type Action = - | { type: 'invalidAuth' } + | { type: "invalidAuth" } | { - type: 'validToken' + type: "validToken" payload: { settings: TokenProviderAuthSettings user: TokenProviderAuthUser | null @@ -47,20 +47,20 @@ type Action = export const reducer = ( state: TokenProviderInternalState, - action: Action + action: Action, ): TokenProviderInternalState => { switch (action.type) { - case 'invalidAuth': + case "invalidAuth": return { ...state, isLoading: false, - isTokenError: true + isTokenError: true, } - case 'validToken': + case "validToken": return { ...state, ...action.payload, - isLoading: false + isLoading: false, } default: return state diff --git a/packages/app-elements/src/providers/TokenProvider/storage.test.ts b/packages/app-elements/src/providers/TokenProvider/storage.test.ts index b9956d70a..47ce7ac14 100644 --- a/packages/app-elements/src/providers/TokenProvider/storage.test.ts +++ b/packages/app-elements/src/providers/TokenProvider/storage.test.ts @@ -1,61 +1,61 @@ -import { getPersistentJWT, makeStorageKey, savePersistentJWT } from './storage' +import { getPersistentJWT, makeStorageKey, savePersistentJWT } from "./storage" -describe('makeStorageKey', () => { - test('should return the storage key for access token', () => { +describe("makeStorageKey", () => { + test("should return the storage key for access token", () => { const key = makeStorageKey({ - appSlug: 'imports', - organizationSlug: 'myorg', - itemType: 'accessToken' + appSlug: "imports", + organizationSlug: "myorg", + itemType: "accessToken", }) - expect(key).toEqual('imports:myorg:accessToken') + expect(key).toEqual("imports:myorg:accessToken") }) - test('should return the storage key for extras', () => { + test("should return the storage key for extras", () => { const key = makeStorageKey({ - appSlug: 'imports', - organizationSlug: 'myorg', - itemType: 'extras' + appSlug: "imports", + organizationSlug: "myorg", + itemType: "extras", }) - expect(key).toEqual('imports:myorg:extras') + expect(key).toEqual("imports:myorg:extras") }) }) -describe('getPersistentJWT', () => { - test('should retrieve the access token by inferring organization slug from URL', () => { - localStorage.setItem('orders:commercelayer:accessToken', 'pre-saved-token') +describe("getPersistentJWT", () => { + test("should retrieve the access token by inferring organization slug from URL", () => { + localStorage.setItem("orders:commercelayer:accessToken", "pre-saved-token") expect( getPersistentJWT({ - appSlug: 'orders', - itemType: 'accessToken' - }) - ).toBe('pre-saved-token') + appSlug: "orders", + itemType: "accessToken", + }), + ).toBe("pre-saved-token") }) - test('should retrieve the access token by using specific organization slug, ignoring URL', () => { - localStorage.setItem('orders:the-red-store:accessToken', 'pre-saved-token2') + test("should retrieve the access token by using specific organization slug, ignoring URL", () => { + localStorage.setItem("orders:the-red-store:accessToken", "pre-saved-token2") expect( getPersistentJWT({ - appSlug: 'orders', - organizationSlug: 'the-red-store', - itemType: 'accessToken' - }) - ).toBe('pre-saved-token2') + appSlug: "orders", + organizationSlug: "the-red-store", + itemType: "accessToken", + }), + ).toBe("pre-saved-token2") }) }) -describe('savePersistentAccessToken', () => { - test('should save the access token by using specific organization slug', () => { +describe("savePersistentAccessToken", () => { + test("should save the access token by using specific organization slug", () => { savePersistentJWT({ - jwt: 'myAccessToken2', - appSlug: 'shipments', - organizationSlug: 'blue-store', - itemType: 'accessToken' + jwt: "myAccessToken2", + appSlug: "shipments", + organizationSlug: "blue-store", + itemType: "accessToken", }) - expect(localStorage.getItem('shipments:demo-store:accessToken')).toBe(null) - expect(localStorage.getItem('shipments:blue-store:accessToken')).toBe( - 'myAccessToken2' + expect(localStorage.getItem("shipments:demo-store:accessToken")).toBe(null) + expect(localStorage.getItem("shipments:blue-store:accessToken")).toBe( + "myAccessToken2", ) }) }) diff --git a/packages/app-elements/src/providers/TokenProvider/storage.ts b/packages/app-elements/src/providers/TokenProvider/storage.ts index 9b29579f8..9fc2ca6ba 100644 --- a/packages/app-elements/src/providers/TokenProvider/storage.ts +++ b/packages/app-elements/src/providers/TokenProvider/storage.ts @@ -1,11 +1,11 @@ -import { type TokenProviderAllowedAppSlug } from './types' +import type { TokenProviderAllowedAppSlug } from "./types" -type ItemType = 'accessToken' | 'extras' +type ItemType = "accessToken" | "extras" export function makeStorageKey({ appSlug, organizationSlug, - itemType + itemType, }: { appSlug: TokenProviderAllowedAppSlug organizationSlug: string @@ -16,8 +16,8 @@ export function makeStorageKey({ export function getPersistentJWT({ appSlug, - organizationSlug = 'commercelayer', - itemType + organizationSlug = "commercelayer", + itemType, }: { /** The app for which to get the token. */ appSlug: TokenProviderAllowedAppSlug @@ -26,7 +26,7 @@ export function getPersistentJWT({ /** The JWT item type you want to retrieve. */ itemType: ItemType }): string | null { - if (typeof window === 'undefined') { + if (typeof window === "undefined") { return null } @@ -34,8 +34,8 @@ export function getPersistentJWT({ makeStorageKey({ appSlug, organizationSlug, - itemType - }) + itemType, + }), ) return storedAccessToken } @@ -43,8 +43,8 @@ export function getPersistentJWT({ export function savePersistentJWT({ appSlug, jwt, - organizationSlug = 'commercelayer', - itemType + organizationSlug = "commercelayer", + itemType, }: { /** The app for which to get the token. */ appSlug: TokenProviderAllowedAppSlug @@ -55,7 +55,7 @@ export function savePersistentJWT({ /** The JWT item type you want to store. */ itemType: ItemType }): void { - if (typeof window === 'undefined') { + if (typeof window === "undefined") { return } @@ -63,8 +63,8 @@ export function savePersistentJWT({ makeStorageKey({ appSlug, organizationSlug, - itemType + itemType, }), - jwt + jwt, ) } diff --git a/packages/app-elements/src/providers/TokenProvider/types.ts b/packages/app-elements/src/providers/TokenProvider/types.ts index 97a0500ac..0de64667d 100644 --- a/packages/app-elements/src/providers/TokenProvider/types.ts +++ b/packages/app-elements/src/providers/TokenProvider/types.ts @@ -1,30 +1,30 @@ -import type { ParsedScopes } from '#providers/TokenProvider/getInfoFromJwt' -import type { ListableResourceType } from '@commercelayer/sdk' +import type { ListableResourceType } from "@commercelayer/sdk" +import type { ParsedScopes } from "#providers/TokenProvider/getInfoFromJwt" export type TokenProviderClAppSlug = - | 'bundles' - | 'customers' - | 'exports' - | 'gift_cards' - | 'imports' - | 'inventory' - | 'orders' - | 'price_lists' - | 'promotions' - | 'returns' - | 'shipments' - | 'sku_lists' - | 'skus' - | 'stock_transfers' - | 'subscriptions' - | 'tags' - | 'webhooks' + | "bundles" + | "customers" + | "exports" + | "gift_cards" + | "imports" + | "inventory" + | "orders" + | "price_lists" + | "promotions" + | "returns" + | "shipments" + | "sku_lists" + | "skus" + | "stock_transfers" + | "subscriptions" + | "tags" + | "webhooks" /** * TokenProviderAllowedApp is a type that contains all the possible kinds of the app that you can create inside the dashboard. * As a convention Commerce Layer official apps have a slug that matches the kind of the app. */ -export type TokenProviderAllowedAppKind = TokenProviderClAppSlug | 'generic' +export type TokenProviderAllowedAppKind = TokenProviderClAppSlug | "generic" /** * @deprecated Use `TokenProviderAllowedAppKind` instead. @@ -35,10 +35,7 @@ export type TokenProviderAllowedApp = TokenProviderAllowedAppKind * The application slug. It could match one of the allowed apps or a custom string. * It is used as the app identifier (e.g. storage key). */ -export type TokenProviderAllowedAppSlug = - | TokenProviderClAppSlug - // eslint-disable-next-line @typescript-eslint/ban-types - | (string & {}) +export type TokenProviderAllowedAppSlug = TokenProviderClAppSlug | (string & {}) /** * TokenProviderTokenApplicationKind is a type that contains all the suitable api credential kinds. @@ -48,25 +45,25 @@ export type TokenProviderAllowedAppSlug = * - an app with its dedicated set of permissions (eg. `order`, `customers`, etc...) */ export type TokenProviderTokenApplicationKind = - | 'integration' - | 'sales_channel' - | 'webapp' - | 'resources' - | 'links' + | "integration" + | "sales_channel" + | "webapp" + | "resources" + | "links" | TokenProviderAllowedAppKind -export type TokenProviderRoleActions = 'create' | 'destroy' | 'read' | 'update' +export type TokenProviderRoleActions = "create" | "destroy" | "read" | "update" export type TokenProviderPermissionItem = Record< TokenProviderRoleActions, boolean > export type TokenProviderRolePermissions = Partial< - Record + Record > interface CoreApiOwnerUser { - type: 'User' + type: "User" id: string first_name: string last_name: string @@ -76,7 +73,7 @@ interface CoreApiOwnerUser { } interface CoreApiOwnerCustomer { - type: 'Customer' + type: "Customer" id: string email: string } @@ -131,7 +128,7 @@ export interface TokenProviderTokenInfo { }> } -export type Mode = 'live' | 'test' +export type Mode = "live" | "test" export interface TokenProviderAuthSettings { /** @@ -186,7 +183,7 @@ export interface TokenProviderAuthUser { displayName: string fullName: string timezone: string - locale: 'en-US' | 'it-IT' + locale: "en-US" | "it-IT" } export interface TokenProviderExtras { diff --git a/packages/app-elements/src/providers/TokenProvider/url.test.ts b/packages/app-elements/src/providers/TokenProvider/url.test.ts index d243b8afb..75a906db4 100644 --- a/packages/app-elements/src/providers/TokenProvider/url.test.ts +++ b/packages/app-elements/src/providers/TokenProvider/url.test.ts @@ -1,15 +1,15 @@ -import { extractDomainFromApiBaseEndpoint } from './url' +import { extractDomainFromApiBaseEndpoint } from "./url" -describe('extractDomainFromApiBaseEndpoint', () => { - test('should return the domain from the apiBaseEndpoint', () => { +describe("extractDomainFromApiBaseEndpoint", () => { + test("should return the domain from the apiBaseEndpoint", () => { expect( - extractDomainFromApiBaseEndpoint('https://demo-store.commercelayer.io') - ).toBe('commercelayer.io') + extractDomainFromApiBaseEndpoint("https://demo-store.commercelayer.io"), + ).toBe("commercelayer.io") expect( extractDomainFromApiBaseEndpoint( - 'https://demo-store.foo.commercelayer.io' - ) - ).toBe('foo.commercelayer.io') + "https://demo-store.foo.commercelayer.io", + ), + ).toBe("foo.commercelayer.io") }) }) diff --git a/packages/app-elements/src/providers/TokenProvider/url.ts b/packages/app-elements/src/providers/TokenProvider/url.ts index 4b35691a4..3c27ac37e 100644 --- a/packages/app-elements/src/providers/TokenProvider/url.ts +++ b/packages/app-elements/src/providers/TokenProvider/url.ts @@ -1,9 +1,9 @@ -import { getCurrentMode } from './getAccessTokenFromUrl' -import { getInfoFromJwt } from './getInfoFromJwt' +import { getCurrentMode } from "./getAccessTokenFromUrl" +import { getInfoFromJwt } from "./getInfoFromJwt" export function makeDashboardUrl({ - domain = 'commercelayer.io', - accessToken + domain = "commercelayer.io", + accessToken, }: { domain?: string accessToken?: string | null @@ -20,10 +20,10 @@ export function makeDashboardUrl({ } export function extractDomainFromApiBaseEndpoint( - apiBaseEndpoint?: string | null + apiBaseEndpoint?: string | null, ): string { if (apiBaseEndpoint == null) { - return 'commercelayer.io' + return "commercelayer.io" } - return apiBaseEndpoint.replace('https://', '').split('.').slice(1).join('.') + return apiBaseEndpoint.replace("https://", "").split(".").slice(1).join(".") } diff --git a/packages/app-elements/src/providers/TokenProvider/validateToken.test.ts b/packages/app-elements/src/providers/TokenProvider/validateToken.test.ts index 47b6c2da1..537da9461 100644 --- a/packages/app-elements/src/providers/TokenProvider/validateToken.test.ts +++ b/packages/app-elements/src/providers/TokenProvider/validateToken.test.ts @@ -1,50 +1,50 @@ -import { isTokenExpired, isValidTokenForCurrentApp } from './validateToken' +import { isTokenExpired, isValidTokenForCurrentApp } from "./validateToken" const token = - 'eyJhbGciOiJIUzUxMiJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJkWGttWkZNcUdSIiwic2x1ZyI6ImdpdXNlcHBlLWltcG9ydHMiLCJlbnRlcnByaXNlIjpmYWxzZX0sImFwcGxpY2F0aW9uIjp7ImlkIjoiYU1hS21pYW5CTiIsImtpbmQiOiJpbnRlZ3JhdGlvbiIsInB1YmxpYyI6ZmFsc2V9LCJ0ZXN0Ijp0cnVlLCJleHAiOjE2NjE4NjA4NzksInJhbmQiOjAuNDIzNzM0OTczNTE3NzY0OCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmNvbW1lcmNlbGF5ZXIuaW8ifQ.yw9TjnpUDUyqeyJ0xv7AS-Suq0TIh7GIAtLyEDvG0yy8t94XM4HojZ6sTU7o963qGOj9Ni7z4wUT4RihWWRpCw' + "eyJhbGciOiJIUzUxMiJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJkWGttWkZNcUdSIiwic2x1ZyI6ImdpdXNlcHBlLWltcG9ydHMiLCJlbnRlcnByaXNlIjpmYWxzZX0sImFwcGxpY2F0aW9uIjp7ImlkIjoiYU1hS21pYW5CTiIsImtpbmQiOiJpbnRlZ3JhdGlvbiIsInB1YmxpYyI6ZmFsc2V9LCJ0ZXN0Ijp0cnVlLCJleHAiOjE2NjE4NjA4NzksInJhbmQiOjAuNDIzNzM0OTczNTE3NzY0OCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmNvbW1lcmNlbGF5ZXIuaW8ifQ.yw9TjnpUDUyqeyJ0xv7AS-Suq0TIh7GIAtLyEDvG0yy8t94XM4HojZ6sTU7o963qGOj9Ni7z4wUT4RihWWRpCw" -describe('isTokenExpired', () => { - test('should check expired token', () => { +describe("isTokenExpired", () => { + test("should check expired token", () => { expect(isTokenExpired({ accessToken: token, compareTo: new Date() })).toBe( - true + true, ) }) - test('should check not expired token', () => { + test("should check not expired token", () => { expect( isTokenExpired({ accessToken: token, - compareTo: new Date('01/01/2022') - }) + compareTo: new Date("01/01/2022"), + }), ).toBe(false) }) - test('token is empty', () => { - expect(isTokenExpired({ accessToken: '', compareTo: new Date() })).toBe( - true + test("token is empty", () => { + expect(isTokenExpired({ accessToken: "", compareTo: new Date() })).toBe( + true, ) }) }) -describe('isValidTokenForCurrentApp', () => { - test('should extract proper data from `tokeninfo` endpoint for `integration` kind', async () => { +describe("isValidTokenForCurrentApp", () => { + test("should extract proper data from `tokeninfo` endpoint for `integration` kind", async () => { const tokenInfo = await isValidTokenForCurrentApp({ accessToken: token, - kind: 'integration', + kind: "integration", isProduction: false, - currentMode: 'test' + currentMode: "test", }) expect(tokenInfo.isValidToken).toBe(true) if (!tokenInfo.isValidToken) { // type guard to make TS happy and be considered as `ValidToken` type - throw new Error('Token is not valid') + throw new Error("Token is not valid") } - expect(tokenInfo.mode).toBe('test') + expect(tokenInfo.mode).toBe("test") - expect(tokenInfo.organizationSlug).toBe('giuseppe-imports') + expect(tokenInfo.organizationSlug).toBe("giuseppe-imports") expect(tokenInfo.user).toBeNull() }) @@ -52,31 +52,31 @@ describe('isValidTokenForCurrentApp', () => { test('should extract proper data from `tokeninfo` endpoint for any other kind (other than "integration")', async () => { const tokenInfo = await isValidTokenForCurrentApp({ accessToken: token, - kind: 'skus', + kind: "skus", isProduction: false, - currentMode: 'test' + currentMode: "test", }) expect(tokenInfo.isValidToken).toBe(true) if (!tokenInfo.isValidToken) { // type guard to make TS happy and be considered as `ValidToken` type - throw new Error('Token is not valid') + throw new Error("Token is not valid") } - expect(tokenInfo.mode).toBe('test') + expect(tokenInfo.mode).toBe("test") - expect(tokenInfo.organizationSlug).toBe('giuseppe-imports') + expect(tokenInfo.organizationSlug).toBe("giuseppe-imports") expect(tokenInfo.user).toStrictEqual({ - id: 'kdRvPYzXfy', - firstName: 'Ringo', - lastName: 'Starr', - email: 'user@commercelayer.io', - timezone: 'Europe/Rome', - displayName: 'R. Starr', - fullName: 'Ringo Starr', - locale: 'en-US' + id: "kdRvPYzXfy", + firstName: "Ringo", + lastName: "Starr", + email: "user@commercelayer.io", + timezone: "Europe/Rome", + displayName: "R. Starr", + fullName: "Ringo Starr", + locale: "en-US", }) }) }) diff --git a/packages/app-elements/src/providers/TokenProvider/validateToken.ts b/packages/app-elements/src/providers/TokenProvider/validateToken.ts index d855b9ca5..9894789e2 100644 --- a/packages/app-elements/src/providers/TokenProvider/validateToken.ts +++ b/packages/app-elements/src/providers/TokenProvider/validateToken.ts @@ -1,22 +1,22 @@ -import { computeFullname, formatDisplayName } from '#helpers/name' -import { type TokenProviderTokenApplicationKind } from '#providers/TokenProvider' -import { getCoreApiBaseEndpoint } from '@commercelayer/js-auth' -import { type ListableResourceType } from '@commercelayer/sdk' -import fetch from 'cross-fetch' -import isEmpty from 'lodash-es/isEmpty' -import { getInfoFromJwt, type ParsedScopes } from './getInfoFromJwt' -import { - type Mode, - type TokenProviderAuthUser, - type TokenProviderClAppSlug, - type TokenProviderPermissionItem, - type TokenProviderRolePermissions, - type TokenProviderTokenInfo -} from './types' +import { getCoreApiBaseEndpoint } from "@commercelayer/js-auth" +import type { ListableResourceType } from "@commercelayer/sdk" +import fetch from "cross-fetch" +import isEmpty from "lodash-es/isEmpty" +import { computeFullname, formatDisplayName } from "#helpers/name" +import type { TokenProviderTokenApplicationKind } from "#providers/TokenProvider" +import { getInfoFromJwt, type ParsedScopes } from "./getInfoFromJwt" +import type { + Mode, + TokenProviderAuthUser, + TokenProviderClAppSlug, + TokenProviderPermissionItem, + TokenProviderRolePermissions, + TokenProviderTokenInfo, +} from "./types" export function isTokenExpired({ accessToken, - compareTo + compareTo, }: { accessToken: string compareTo: Date @@ -50,7 +50,7 @@ export async function isValidTokenForCurrentApp({ kind, isProduction, currentMode, - organizationSlug + organizationSlug, }: { accessToken: string kind: TokenProviderTokenApplicationKind @@ -66,33 +66,33 @@ export async function isValidTokenForCurrentApp({ if (jwtInfo.orgSlug == null) { return { - isValidToken: false + isValidToken: false, } } // this means we are trying to use a token for a different mode (live|test) the app is running on if (jwtInfo.mode !== currentMode) { return { - isValidToken: false + isValidToken: false, } } try { const tokenInfo: - | (Omit & { + | (Omit & { token: { test: boolean } }) | null = - kind === 'integration' + kind === "integration" ? { permissions: {}, token: { - test: jwtInfo.mode !== 'live' - } + test: jwtInfo.mode !== "live", + }, } : await fetchTokenInfo({ accessToken, - orgSlug: jwtInfo.orgSlug + orgSlug: jwtInfo.orgSlug, }) const isValidOnCore = Boolean(tokenInfo?.token) @@ -106,23 +106,23 @@ export async function isValidTokenForCurrentApp({ // running validation only in production if (isProduction && !isAllValid) { console.error( - 'Invalid token. Please check if token is valid and if you have properly set your organization slug.', + "Invalid token. Please check if token is valid and if you have properly set your organization slug.", { tokenInfo, isValidKind, isValidOrganizationSlug, - isValidOnCore - } + isValidOnCore, + }, ) return { - isValidToken: false + isValidToken: false, } } return { isValidToken: true, accessToken, - mode: tokenInfo?.token.test === true ? 'test' : 'live', + mode: tokenInfo?.token.test === true ? "test" : "live", organizationSlug: jwtInfo.orgSlug, permissions: tokenInfo?.permissions != null @@ -132,10 +132,10 @@ export async function isValidTokenForCurrentApp({ tokenInfo?.accessible_apps != null ? tokenInfo?.accessible_apps .map((app) => app.kind) - .filter((kind) => kind !== 'generic') + .filter((kind) => kind !== "generic") : undefined, user: - tokenInfo?.owner != null && tokenInfo.owner.type === 'User' + tokenInfo?.owner != null && tokenInfo.owner.type === "User" ? { id: tokenInfo.owner.id, email: tokenInfo.owner.email, @@ -144,27 +144,26 @@ export async function isValidTokenForCurrentApp({ timezone: tokenInfo.owner.time_zone, displayName: formatDisplayName( tokenInfo.owner.first_name, - tokenInfo.owner.last_name + tokenInfo.owner.last_name, ), fullName: computeFullname( tokenInfo.owner.first_name, - tokenInfo.owner.last_name + tokenInfo.owner.last_name, ), - locale: 'en-US' // setting a default for now, then this will probably arrive from tokenInfo.owner.locale + locale: "en-US", // setting a default for now, then this will probably arrive from tokenInfo.owner.locale } : null, - scopes: jwtInfo.scopes + scopes: jwtInfo.scopes, } } catch { return { - isValidToken: false + isValidToken: false, } } } async function fetchTokenInfo({ accessToken, - orgSlug }: { accessToken: string orgSlug: string @@ -174,9 +173,9 @@ async function fetchTokenInfo({ const tokenInfoResponse = await fetch( `${coreApiBaseEndpoint}/oauth/tokeninfo`, { - method: 'GET', - headers: { authorization: `Bearer ${accessToken}` } - } + method: "GET", + headers: { authorization: `Bearer ${accessToken}` }, + }, ) return await tokenInfoResponse.json() } catch { @@ -185,23 +184,24 @@ async function fetchTokenInfo({ } function preparePermissions( - apiPermissions: TokenProviderTokenInfo['permissions'] + apiPermissions: TokenProviderTokenInfo["permissions"], ): TokenProviderRolePermissions { const resourceList = Object.keys(apiPermissions) as ListableResourceType[] return resourceList.reduce( (permissions, resource) => { const permissionItem: TokenProviderPermissionItem = { - create: apiPermissions[resource]?.actions.includes('create') ?? false, - destroy: apiPermissions[resource]?.actions.includes('destroy') ?? false, - read: apiPermissions[resource]?.actions.includes('read') ?? false, - update: apiPermissions[resource]?.actions.includes('update') ?? false + create: apiPermissions[resource]?.actions.includes("create") ?? false, + destroy: apiPermissions[resource]?.actions.includes("destroy") ?? false, + read: apiPermissions[resource]?.actions.includes("read") ?? false, + update: apiPermissions[resource]?.actions.includes("update") ?? false, } + return { ...permissions, - [resource]: permissionItem + [resource]: permissionItem, } }, - {} + {}, ) } diff --git a/packages/app-elements/src/providers/createApp.tsx b/packages/app-elements/src/providers/createApp.tsx index fb9b95a7e..fd95a07c8 100644 --- a/packages/app-elements/src/providers/createApp.tsx +++ b/packages/app-elements/src/providers/createApp.tsx @@ -1,8 +1,8 @@ -import isEmpty from 'lodash-es/isEmpty' -import { type ReactNode } from 'react' -import ReactDOM, { type Root } from 'react-dom/client' -import { type TokenProviderProps } from './TokenProvider/TokenProvider' -import { type TokenProviderAllowedAppSlug } from './TokenProvider/types' +import isEmpty from "lodash-es/isEmpty" +import type { ReactNode } from "react" +import ReactDOM, { type Root } from "react-dom/client" +import type { TokenProviderProps } from "./TokenProvider/TokenProvider" +import type { TokenProviderAllowedAppSlug } from "./TokenProvider/types" export type ClAppKey = `clApp_${TokenProviderAllowedAppSlug}` @@ -28,7 +28,7 @@ declare global { } export interface ClAppProps - extends Partial> { + extends Partial> { /** * Base path for internal routing. * Example: `my-app` if you want to serve the app at `https://my-domain.com/my-app/`. @@ -43,7 +43,7 @@ export interface ClAppProps **/ export function createApp( children: (props: ClAppProps) => ReactNode, - appSlug: TokenProviderAllowedAppSlug + appSlug: TokenProviderAllowedAppSlug, ): void { window[`clApp_${appSlug}`] = { init: (node, props) => { @@ -56,12 +56,12 @@ export function createApp( children({ ...props, organizationSlug: parseOrganizationSlug(props?.organizationSlug), - routerBase: parseRouterBase(props?.routerBase) - }) + routerBase: parseRouterBase(props?.routerBase), + }), ) return root - } + }, } } @@ -76,7 +76,7 @@ function parseRouterBase(path?: string): string | undefined { return } - if (path.startsWith('/')) { + if (path.startsWith("/")) { return path } else { return `/${path}` diff --git a/packages/app-elements/src/styles/global.css b/packages/app-elements/src/styles/global.css index 61e3bfcbf..169b3a6cf 100644 --- a/packages/app-elements/src/styles/global.css +++ b/packages/app-elements/src/styles/global.css @@ -3,4 +3,3 @@ @import "tailwindcss/utilities"; @import "./vendor.css"; - diff --git a/packages/app-elements/src/ui/atoms/A.test.tsx b/packages/app-elements/src/ui/atoms/A.test.tsx index fc52072dc..490a486b4 100644 --- a/packages/app-elements/src/ui/atoms/A.test.tsx +++ b/packages/app-elements/src/ui/atoms/A.test.tsx @@ -1,86 +1,86 @@ -import { render } from '@testing-library/react' -import { A } from './A' +import { render } from "@testing-library/react" +import { A } from "./A" -describe('Anchor', () => { - it('Should be rendered', () => { +describe("Anchor", () => { + it("Should be rendered", () => { const { getByRole } = render( - My anchor tag + My anchor tag, ) - const a = getByRole('link') + const a = getByRole("link") expect(a).toBeVisible() - expect(a.innerHTML).toBe('My anchor tag') - expect(a.tagName).toBe('A') + expect(a.innerHTML).toBe("My anchor tag") + expect(a.tagName).toBe("A") expect(a).toBeInstanceOf(HTMLAnchorElement) - expect(a.className).toContain('text-primary') - expect(a.getAttribute('href')).toBe('https://commercelayer.io') + expect(a.className).toContain("text-primary") + expect(a.getAttribute("href")).toBe("https://commercelayer.io") }) - it('Should render as primary variant', () => { + it("Should render as primary variant", () => { const { getByRole } = render( - + click me - + , ) - expect(getByRole('link').className).toContain('bg-black') - expect(getByRole('link').className).toContain('text-white') + expect(getByRole("link").className).toContain("bg-black") + expect(getByRole("link").className).toContain("text-white") }) - it('Should render as secondary variant', () => { + it("Should render as secondary variant", () => { const { getByRole } = render( - + click me - + , ) - expect(getByRole('link').className).toContain('bg-white') - expect(getByRole('link').className).toContain('text-black') - expect(getByRole('link').className).toContain('border border-black') + expect(getByRole("link").className).toContain("bg-white") + expect(getByRole("link").className).toContain("text-black") + expect(getByRole("link").className).toContain("border border-black") }) - it('Should render as danger variant', () => { + it("Should render as danger variant", () => { const { getByRole } = render( - + click me - + , ) - expect(getByRole('link').className).toContain('bg-white') - expect(getByRole('link').className).toContain('text-red') - expect(getByRole('link').className).toContain('border border-red') + expect(getByRole("link").className).toContain("bg-white") + expect(getByRole("link").className).toContain("text-red") + expect(getByRole("link").className).toContain("border border-red") }) - it('Should render as size small', () => { + it("Should render as size small", () => { const { getByRole } = render( - + click me - + , ) - expect(getByRole('link').className).toContain('px-4 py-2') + expect(getByRole("link").className).toContain("px-4 py-2") }) - it('Should render as size regular (default)', () => { + it("Should render as size regular (default)", () => { const { getByRole } = render( - + click me - + , ) - expect(getByRole('link').className).toContain('px-6 py-3') + expect(getByRole("link").className).toContain("px-6 py-3") }) - it('Should render as size large', () => { + it("Should render as size large", () => { const { getByRole } = render( - + click me - + , ) - expect(getByRole('link').className).toContain('px-8 py-4') + expect(getByRole("link").className).toContain("px-8 py-4") }) - it('Should render with flex alignment', () => { + it("Should render with flex alignment", () => { const { getByRole } = render( - + click me - + , ) - expect(getByRole('link').className).toContain('flex gap-1 items-center') + expect(getByRole("link").className).toContain("flex gap-1 items-center") }) }) diff --git a/packages/app-elements/src/ui/atoms/A.tsx b/packages/app-elements/src/ui/atoms/A.tsx index c2a70d56e..48124822f 100644 --- a/packages/app-elements/src/ui/atoms/A.tsx +++ b/packages/app-elements/src/ui/atoms/A.tsx @@ -1,13 +1,13 @@ -import cn from 'classnames' -import { type SetRequired } from 'type-fest' +import cn from "classnames" +import type { SetRequired } from "type-fest" import { getInteractiveElementClassName, - type InteractiveElementProps -} from '../internals/InteractiveElement.className' + type InteractiveElementProps, +} from "../internals/InteractiveElement.className" export type AProps = SetRequired< React.AnchorHTMLAttributes, - 'href' + "href" > & InteractiveElementProps @@ -22,8 +22,8 @@ export const A: React.FC = ({ disabled, fullWidth, href, - size = 'regular', - variant = 'link', + size = "regular", + variant = "link", ...rest }) => { return ( @@ -36,8 +36,8 @@ export const A: React.FC = ({ disabled, fullWidth, size, - variant - }) + variant, + }), )} aria-disabled={disabled} href={href} @@ -48,4 +48,4 @@ export const A: React.FC = ({ ) } -A.displayName = 'A' +A.displayName = "A" diff --git a/packages/app-elements/src/ui/atoms/Alert.test.tsx b/packages/app-elements/src/ui/atoms/Alert.test.tsx index b00f88d4d..214633f1e 100644 --- a/packages/app-elements/src/ui/atoms/Alert.test.tsx +++ b/packages/app-elements/src/ui/atoms/Alert.test.tsx @@ -1,15 +1,15 @@ -import { render } from '@testing-library/react' -import { Alert } from './Alert' +import { render } from "@testing-library/react" +import { Alert } from "./Alert" -describe('Alert', () => { - test('Should be rendered', () => { +describe("Alert", () => { + test("Should be rendered", () => { const { getByRole } = render( - Ehi, this is a warning! + Ehi, this is a warning!, ) - const alert = getByRole('alert') + const alert = getByRole("alert") expect(alert).toBeVisible() - expect(alert.textContent).toBe('Ehi, this is a warning!') - expect(alert.querySelector('svg')).toBeVisible() + expect(alert.textContent).toBe("Ehi, this is a warning!") + expect(alert.querySelector("svg")).toBeVisible() }) }) diff --git a/packages/app-elements/src/ui/atoms/Alert.tsx b/packages/app-elements/src/ui/atoms/Alert.tsx index a95114e53..391c17bc5 100644 --- a/packages/app-elements/src/ui/atoms/Alert.tsx +++ b/packages/app-elements/src/ui/atoms/Alert.tsx @@ -1,15 +1,15 @@ import { CheckCircle, + type Icon, Info, Warning, XCircle, - type Icon -} from '@phosphor-icons/react' -import classNames from 'classnames' +} from "@phosphor-icons/react" +import classNames from "classnames" export interface AlertProps { /** Alert status. This affects the color scheme and icon used. */ - status: 'error' | 'success' | 'warning' | 'info' + status: "error" | "success" | "warning" | "info" children: React.ReactNode } @@ -21,27 +21,27 @@ export const Alert: React.FC = ({ children, status }) => { return (
-
- +
+
{children}
) } -Alert.displayName = 'Alert' +Alert.displayName = "Alert" -const icons: Record = { +const icons: Record = { warning: Warning, error: XCircle, info: Info, - success: CheckCircle + success: CheckCircle, } diff --git a/packages/app-elements/src/ui/atoms/Avatar.test.tsx b/packages/app-elements/src/ui/atoms/Avatar.test.tsx index f5a9d8893..df36f31cb 100644 --- a/packages/app-elements/src/ui/atoms/Avatar.test.tsx +++ b/packages/app-elements/src/ui/atoms/Avatar.test.tsx @@ -1,6 +1,6 @@ -import { render, type RenderResult } from '@testing-library/react' -import { Avatar, type AvatarProps } from './Avatar' -import { presets } from './Avatar.utils' +import { type RenderResult, render } from "@testing-library/react" +import { Avatar, type AvatarProps } from "./Avatar" +import { presets } from "./Avatar.utils" interface SetupProps extends AvatarProps { id: string @@ -15,49 +15,49 @@ const setup = ({ id, ...props }: SetupProps): SetupResult => { const element = utils.getByTestId(id) return { element, - ...utils + ...utils, } } -describe('Avatar', () => { - test('Should be rendered with src pointing to an https url', () => { +describe("Avatar", () => { + test("Should be rendered with src pointing to an https url", () => { const { element } = setup({ - id: 'avatar', - src: 'https://i2.wp.com/ui-avatars.com/api/Commerce+Layer/160/FF656B/FFFFFF/2/0.33/true/true/true?ssl=1', - alt: 'Commerce Layer logo' + id: "avatar", + src: "https://i2.wp.com/ui-avatars.com/api/Commerce+Layer/160/FF656B/FFFFFF/2/0.33/true/true/true?ssl=1", + alt: "Commerce Layer logo", }) expect(element).toBeVisible() expect(element).toMatchSnapshot() - expect(element.getAttribute('src')).toBe( - 'https://i2.wp.com/ui-avatars.com/api/Commerce+Layer/160/FF656B/FFFFFF/2/0.33/true/true/true?ssl=1' + expect(element.getAttribute("src")).toBe( + "https://i2.wp.com/ui-avatars.com/api/Commerce+Layer/160/FF656B/FFFFFF/2/0.33/true/true/true?ssl=1", ) - expect(element.getAttribute('alt')).toBe('Commerce Layer logo') + expect(element.getAttribute("alt")).toBe("Commerce Layer logo") }) - test('Should be rendered with src pointing to a preset', () => { + test("Should be rendered with src pointing to a preset", () => { const { element } = setup({ - id: 'avatar', - src: 'payments:stripe', - alt: 'Stripe' + id: "avatar", + src: "payments:stripe", + alt: "Stripe", }) expect(element).toBeVisible() expect(element).toMatchSnapshot() - expect(element.getAttribute('src')).toContain(presets['payments:stripe']) - expect(element.getAttribute('alt')).toBe('Stripe') + expect(element.getAttribute("src")).toContain(presets["payments:stripe"]) + expect(element.getAttribute("alt")).toBe("Stripe") }) - test('Should be rendered with a placeholder image when src is not defined', () => { + test("Should be rendered with a placeholder image when src is not defined", () => { const { element } = setup({ - id: 'avatar', + id: "avatar", // @ts-expect-error I want to test this scenario. src: undefined, - alt: 'Undefined source' + alt: "Undefined source", }) expect(element).toBeVisible() expect(element).toMatchSnapshot() - expect(element.getAttribute('src')).toContain( - 'https://data.commercelayer.app/assets/images/icons/items/placeholder.svg' + expect(element.getAttribute("src")).toContain( + "https://data.commercelayer.app/assets/images/icons/items/placeholder.svg", ) - expect(element.getAttribute('alt')).toBe('Undefined source') + expect(element.getAttribute("alt")).toBe("Undefined source") }) }) diff --git a/packages/app-elements/src/ui/atoms/Avatar.tsx b/packages/app-elements/src/ui/atoms/Avatar.tsx index 533721cf4..4c6a55a9a 100644 --- a/packages/app-elements/src/ui/atoms/Avatar.tsx +++ b/packages/app-elements/src/ui/atoms/Avatar.tsx @@ -1,6 +1,6 @@ -import cn from 'classnames' -import { useState, type JSX } from 'react' -import { presets } from './Avatar.utils' +import cn from "classnames" +import { type JSX, useState } from "react" +import { presets } from "./Avatar.utils" type SrcPreset = keyof typeof presets type SrcUrl = `https://${string}` | `data:image/${string}` @@ -17,18 +17,18 @@ export interface AvatarProps { /** * Specify `none` to remove border */ - border?: 'none' + border?: "none" /** * Image shape * @default "rounded" */ - shape?: 'rounded' | 'circle' + shape?: "rounded" | "circle" /** * Image size * (x-small: 32px, small: 48px, normal: 58px, large: 72px) * @default "normal" */ - size?: 'x-small' | 'small' | 'normal' | 'large' + size?: "x-small" | "small" | "normal" | "large" /** * Image class */ @@ -42,8 +42,8 @@ export function Avatar({ src, alt, border, - shape = 'rounded', - size = 'normal', + shape = "rounded", + size = "normal", className, ...rest }: AvatarProps): JSX.Element { @@ -66,43 +66,43 @@ export function Avatar({ } alt={alt} className={cn( - 'border object-contain object-center', + "border object-contain object-center", { // size - 'min-w-[72px] min-h-[72px] w-[72px] h-[72px]': size === 'large', - 'min-w-[58px] min-h-[58px] w-[58px] h-[58px]': size === 'normal', - 'min-w-[42px] min-h-[42px] w-[42px] h-[42px]': size === 'small', - 'min-w-8 min-h-8 w-8 h-8': size === 'x-small', + "min-w-[72px] min-h-[72px] w-[72px] h-[72px]": size === "large", + "min-w-[58px] min-h-[58px] w-[58px] h-[58px]": size === "normal", + "min-w-[42px] min-h-[42px] w-[42px] h-[42px]": size === "small", + "min-w-8 min-h-8 w-8 h-8": size === "x-small", // shape - rounded: shape === 'rounded', - 'rounded-full': shape === 'circle', + rounded: shape === "rounded", + "rounded-full": shape === "circle", // border - 'border-gray-100': border == null, - 'border-transparent': - border === 'none' || (srcIsValidPreset(src) && src !== 'gift_card'), + "border-gray-100": border == null, + "border-transparent": + border === "none" || (srcIsValidPreset(src) && src !== "gift_card"), // placeholder - 'p-1': hasError && (size === 'normal' || size === 'large'), - 'p-0.5': hasError && size !== 'normal' + "p-1": hasError && (size === "normal" || size === "large"), + "p-0.5": hasError && size !== "normal", }, - className + className, )} /> ) } -function srcIsValidPreset(src: AvatarProps['src']): src is SrcPreset { +function srcIsValidPreset(src: AvatarProps["src"]): src is SrcPreset { return Object.keys(presets).includes(src) } function srcIsValidUrl( - src: AvatarProps['src'] | undefined | null + src: AvatarProps["src"] | undefined | null, ): src is SrcUrl { return ( - src != null && (src.startsWith('https://') || src.startsWith('data:image/')) + src != null && (src.startsWith("https://") || src.startsWith("data:image/")) ) } const placeholderSvg = - 'https://data.commercelayer.app/assets/images/icons/items/placeholder.svg' + "https://data.commercelayer.app/assets/images/icons/items/placeholder.svg" -Avatar.displayName = 'Avatar' +Avatar.displayName = "Avatar" diff --git a/packages/app-elements/src/ui/atoms/Avatar.utils.ts b/packages/app-elements/src/ui/atoms/Avatar.utils.ts index 2b0479eb8..187624e46 100644 --- a/packages/app-elements/src/ui/atoms/Avatar.utils.ts +++ b/packages/app-elements/src/ui/atoms/Avatar.utils.ts @@ -1,40 +1,40 @@ export const presets = { gift_card: - 'https://data.commercelayer.app/assets/images/icons/items/gift-card.svg', - 'carriers:generic': - 'https://data.commercelayer.app/assets/images/icons/carriers/generic.svg', - 'carriers:fedex': - 'https://data.commercelayer.app/assets/images/icons/carriers/fedex.svg', - 'carriers:ups': - 'https://data.commercelayer.app/assets/images/icons/carriers/ups.svg', - 'carriers:dhl': - 'https://data.commercelayer.app/assets/images/icons/carriers/dhl.svg', - 'payments:stripe': - 'https://data.commercelayer.app/assets/images/icons/payment-gateways/stripe.svg', - 'payments:braintree': - 'https://data.commercelayer.app/assets/images/icons/payment-gateways/braintree.svg', - 'payments:paypal': - 'https://data.commercelayer.app/assets/images/icons/payment-gateways/paypal.svg', - 'payments:klarna': - 'https://data.commercelayer.app/assets/images/icons/payment-gateways/klarna.svg', - 'payments:checkout': - 'https://data.commercelayer.app/assets/images/icons/payment-gateways/checkout.svg', - 'payments:adyen': - 'https://data.commercelayer.app/assets/images/icons/payment-gateways/adyen.svg', - 'payments:axerve': - 'https://data.commercelayer.app/assets/images/icons/payment-gateways/axerve.svg', - 'payments:satispay': - 'https://data.commercelayer.app/assets/images/icons/payment-gateways/satispay.svg', - 'payments:manual': - 'https://data.commercelayer.app/assets/images/icons/payment-gateways/manual-service.svg', - 'payments:external': - 'https://data.commercelayer.app/assets/images/icons/payment-gateways/external-service.svg', - 'tax-calculators:avalara': - 'https://data.commercelayer.app/assets/images/icons/tax-calculators/avalara.svg', - 'tax-calculators:taxjar': - 'https://data.commercelayer.app/assets/images/icons/tax-calculators/taxjar.svg', - 'tax-calculators:manual': - 'https://data.commercelayer.app/assets/images/icons/tax-calculators/manual-service.svg', - 'tax-calculators:external': - 'https://data.commercelayer.app/assets/images/icons/tax-calculators/external-service.svg' + "https://data.commercelayer.app/assets/images/icons/items/gift-card.svg", + "carriers:generic": + "https://data.commercelayer.app/assets/images/icons/carriers/generic.svg", + "carriers:fedex": + "https://data.commercelayer.app/assets/images/icons/carriers/fedex.svg", + "carriers:ups": + "https://data.commercelayer.app/assets/images/icons/carriers/ups.svg", + "carriers:dhl": + "https://data.commercelayer.app/assets/images/icons/carriers/dhl.svg", + "payments:stripe": + "https://data.commercelayer.app/assets/images/icons/payment-gateways/stripe.svg", + "payments:braintree": + "https://data.commercelayer.app/assets/images/icons/payment-gateways/braintree.svg", + "payments:paypal": + "https://data.commercelayer.app/assets/images/icons/payment-gateways/paypal.svg", + "payments:klarna": + "https://data.commercelayer.app/assets/images/icons/payment-gateways/klarna.svg", + "payments:checkout": + "https://data.commercelayer.app/assets/images/icons/payment-gateways/checkout.svg", + "payments:adyen": + "https://data.commercelayer.app/assets/images/icons/payment-gateways/adyen.svg", + "payments:axerve": + "https://data.commercelayer.app/assets/images/icons/payment-gateways/axerve.svg", + "payments:satispay": + "https://data.commercelayer.app/assets/images/icons/payment-gateways/satispay.svg", + "payments:manual": + "https://data.commercelayer.app/assets/images/icons/payment-gateways/manual-service.svg", + "payments:external": + "https://data.commercelayer.app/assets/images/icons/payment-gateways/external-service.svg", + "tax-calculators:avalara": + "https://data.commercelayer.app/assets/images/icons/tax-calculators/avalara.svg", + "tax-calculators:taxjar": + "https://data.commercelayer.app/assets/images/icons/tax-calculators/taxjar.svg", + "tax-calculators:manual": + "https://data.commercelayer.app/assets/images/icons/tax-calculators/manual-service.svg", + "tax-calculators:external": + "https://data.commercelayer.app/assets/images/icons/tax-calculators/external-service.svg", } diff --git a/packages/app-elements/src/ui/atoms/AvatarLetter/AvatarLetter.test.tsx b/packages/app-elements/src/ui/atoms/AvatarLetter/AvatarLetter.test.tsx index 02e2856a6..e914fc004 100644 --- a/packages/app-elements/src/ui/atoms/AvatarLetter/AvatarLetter.test.tsx +++ b/packages/app-elements/src/ui/atoms/AvatarLetter/AvatarLetter.test.tsx @@ -1,14 +1,14 @@ -import { render } from '@testing-library/react' -import { AvatarLetter } from './AvatarLetter' +import { render } from "@testing-library/react" +import { AvatarLetter } from "./AvatarLetter" -describe('AvatarLetter', () => { - it('when the `text` is composed of more than one word, it will use the initials from the first two words.', () => { - const { getByText } = render() - expect(getByText('CL')).toBeInTheDocument() +describe("AvatarLetter", () => { + it("when the `text` is composed of more than one word, it will use the initials from the first two words.", () => { + const { getByText } = render() + expect(getByText("CL")).toBeInTheDocument() }) - it('when the `text` is composed of one single word, it will use the first two chars of the text.', () => { - const { getByText } = render() - expect(getByText('DO')).toBeInTheDocument() + it("when the `text` is composed of one single word, it will use the first two chars of the text.", () => { + const { getByText } = render() + expect(getByText("DO")).toBeInTheDocument() }) }) diff --git a/packages/app-elements/src/ui/atoms/AvatarLetter/AvatarLetter.tsx b/packages/app-elements/src/ui/atoms/AvatarLetter/AvatarLetter.tsx index 331cb71d8..310bdcfa8 100644 --- a/packages/app-elements/src/ui/atoms/AvatarLetter/AvatarLetter.tsx +++ b/packages/app-elements/src/ui/atoms/AvatarLetter/AvatarLetter.tsx @@ -1,7 +1,7 @@ -import { getDeterministicValue, getInitials } from '#utils/text' -import classNames from 'classnames' -import { useMemo, type JSX } from 'react' -import { BG_COLORS, getTextColorForBackground } from './colors' +import classNames from "classnames" +import { type JSX, useMemo } from "react" +import { getDeterministicValue, getInitials } from "#utils/text" +import { BG_COLORS, getTextColorForBackground } from "./colors" export interface AvatarLetterProps { /** @@ -35,19 +35,19 @@ export function AvatarLetter({ }: AvatarLetterProps): JSX.Element { const initials = useMemo(() => getInitials(text), [text]) const backgroundColor = useMemo( - () => getDeterministicValue(text, BG_COLORS) ?? '#FFFFFF', - [text] + () => getDeterministicValue(text, BG_COLORS) ?? "#FFFFFF", + [text], ) const textColor = useMemo( () => getTextColorForBackground(backgroundColor), - [backgroundColor] + [backgroundColor], ) if (children != null) { return children({ initials, backgroundColor, - textColor + textColor, }) } @@ -55,17 +55,17 @@ export function AvatarLetter({
@@ -74,4 +74,4 @@ export function AvatarLetter({ ) } -AvatarLetter.displayName = 'AvatarLetter' +AvatarLetter.displayName = "AvatarLetter" diff --git a/packages/app-elements/src/ui/atoms/AvatarLetter/colors.test.ts b/packages/app-elements/src/ui/atoms/AvatarLetter/colors.test.ts index d4cc12251..f05676faa 100644 --- a/packages/app-elements/src/ui/atoms/AvatarLetter/colors.test.ts +++ b/packages/app-elements/src/ui/atoms/AvatarLetter/colors.test.ts @@ -1,12 +1,12 @@ -import { getTextColorForBackground } from './colors' +import { getTextColorForBackground } from "./colors" -describe('getTextColorForBackground', () => { - it('should return black or white according to the best contrast ratio.', () => { - expect(getTextColorForBackground('#FFFFFF')).toEqual('black') - expect(getTextColorForBackground('#000000')).toEqual('white') - expect(getTextColorForBackground('#FF0000')).toEqual('black') - expect(getTextColorForBackground('#00FF00')).toEqual('black') - expect(getTextColorForBackground('#0000FF')).toEqual('white') - expect(getTextColorForBackground('#2196F3')).toEqual('black') +describe("getTextColorForBackground", () => { + it("should return black or white according to the best contrast ratio.", () => { + expect(getTextColorForBackground("#FFFFFF")).toEqual("black") + expect(getTextColorForBackground("#000000")).toEqual("white") + expect(getTextColorForBackground("#FF0000")).toEqual("black") + expect(getTextColorForBackground("#00FF00")).toEqual("black") + expect(getTextColorForBackground("#0000FF")).toEqual("white") + expect(getTextColorForBackground("#2196F3")).toEqual("black") }) }) diff --git a/packages/app-elements/src/ui/atoms/AvatarLetter/colors.ts b/packages/app-elements/src/ui/atoms/AvatarLetter/colors.ts index 00fffbe56..a0158b0dd 100644 --- a/packages/app-elements/src/ui/atoms/AvatarLetter/colors.ts +++ b/packages/app-elements/src/ui/atoms/AvatarLetter/colors.ts @@ -1,39 +1,38 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { asUniqueArray } from '#utils/array' +import { asUniqueArray } from "#utils/array" export const BG_COLORS = asUniqueArray([ - '#BBBEBE', - '#79E4F8', - '#FFCC80', - '#FFF280', - '#83F2C2', - '#18D0F3', - '#9CB1FF', - '#FFEA2E', - '#FF8E92', - '#1FDA8A', - '#FFAB2E', - '#FE84BA', - '#BDAA00', - '#686E6E', - '#11784C', - '#BC0007', - '#942E0C', - '#A00148', - '#322AD8', - '#343535' + "#BBBEBE", + "#79E4F8", + "#FFCC80", + "#FFF280", + "#83F2C2", + "#18D0F3", + "#9CB1FF", + "#FFEA2E", + "#FF8E92", + "#1FDA8A", + "#FFAB2E", + "#FE84BA", + "#BDAA00", + "#686E6E", + "#11784C", + "#BC0007", + "#942E0C", + "#A00148", + "#322AD8", + "#343535", ]) export function getTextColorForBackground( - backgroundColor: string -): 'black' | 'white' { + backgroundColor: string, +): "black" | "white" { /** * Convert a hexadecimal format to RGB format. */ const hexToRgb = (hex: string): { r: number; g: number; b: number } => ({ r: parseInt(hex.slice(1, 3), 16), g: parseInt(hex.slice(3, 5), 16), - b: parseInt(hex.slice(5, 7), 16) + b: parseInt(hex.slice(5, 7), 16), }) /** @@ -43,7 +42,7 @@ export function getTextColorForBackground( const calculateRelativeLuminance = ({ r, g, - b + b, }: { r: number g: number @@ -51,7 +50,7 @@ export function getTextColorForBackground( }): number => { const sRGB = (c: number): number => { const sc = c / 255 - return sc <= 0.03928 ? sc / 12.92 : Math.pow((sc + 0.055) / 1.055, 2.4) + return sc <= 0.03928 ? sc / 12.92 : ((sc + 0.055) / 1.055) ** 2.4 } return 0.2126 * sRGB(r) + 0.7152 * sRGB(g) + 0.0722 * sRGB(b) @@ -70,14 +69,14 @@ export function getTextColorForBackground( * This results in a value ranging from `1:1` (no contrast at all) to `21:1` (the highest possible contrast). */ const calculateWhiteAndBlackContrastRatio = ( - luminance: number + luminance: number, ): { white: number; black: number } => { - const whiteLuminance = calculateRelativeLuminance(hexToRgb('#FFFFFF')) - const blackLuminance = calculateRelativeLuminance(hexToRgb('#000000')) + const whiteLuminance = calculateRelativeLuminance(hexToRgb("#FFFFFF")) + const blackLuminance = calculateRelativeLuminance(hexToRgb("#000000")) return { white: (whiteLuminance + 0.05) / (luminance + 0.05), - black: (luminance + 0.05) / (blackLuminance + 0.05) + black: (luminance + 0.05) / (blackLuminance + 0.05), } } @@ -87,5 +86,5 @@ export function getTextColorForBackground( const { white, black } = calculateWhiteAndBlackContrastRatio(luminance) - return white > black ? 'white' : 'black' + return white > black ? "white" : "black" } diff --git a/packages/app-elements/src/ui/atoms/AvatarLetter/index.tsx b/packages/app-elements/src/ui/atoms/AvatarLetter/index.tsx index b249320d4..20113e6bb 100644 --- a/packages/app-elements/src/ui/atoms/AvatarLetter/index.tsx +++ b/packages/app-elements/src/ui/atoms/AvatarLetter/index.tsx @@ -1 +1 @@ -export { AvatarLetter, type AvatarLetterProps } from './AvatarLetter' +export { AvatarLetter, type AvatarLetterProps } from "./AvatarLetter" diff --git a/packages/app-elements/src/ui/atoms/Badge/Badge.test.tsx b/packages/app-elements/src/ui/atoms/Badge/Badge.test.tsx index bbce78ccb..1b028e3d0 100644 --- a/packages/app-elements/src/ui/atoms/Badge/Badge.test.tsx +++ b/packages/app-elements/src/ui/atoms/Badge/Badge.test.tsx @@ -1,10 +1,10 @@ -import { render, type RenderResult } from '@testing-library/react' -import { Badge, type BadgeProps } from './Badge' +import { type RenderResult, render } from "@testing-library/react" +import { Badge, type BadgeProps } from "./Badge" interface SetupProps { id: string children: string - variant: BadgeProps['variant'] + variant: BadgeProps["variant"] } type SetupResult = RenderResult & { @@ -16,19 +16,19 @@ const setup = ({ id, ...rest }: SetupProps): SetupResult => { const element = utils.getByTestId(id) return { element, - ...utils + ...utils, } } -describe('Badge', () => { - test('Should be rendered', () => { +describe("Badge", () => { + test("Should be rendered", () => { const { element } = setup({ - id: 'my-badge', - children: 'Completed', - variant: 'success' + id: "my-badge", + children: "Completed", + variant: "success", }) expect(element).toBeInTheDocument() - expect(element.tagName).toBe('DIV') - expect(element.innerHTML).toContain('Completed') + expect(element.tagName).toBe("DIV") + expect(element.innerHTML).toContain("Completed") }) }) diff --git a/packages/app-elements/src/ui/atoms/Badge/Badge.tsx b/packages/app-elements/src/ui/atoms/Badge/Badge.tsx index 403c3d820..2dbe4c229 100644 --- a/packages/app-elements/src/ui/atoms/Badge/Badge.tsx +++ b/packages/app-elements/src/ui/atoms/Badge/Badge.tsx @@ -1,13 +1,13 @@ -import { Icon, type IconProps } from '#ui/atoms/Icon' -import cn from 'classnames' -import React from 'react' -import { variantCss } from './badgeVariants' +import cn from "classnames" +import type React from "react" +import { Icon, type IconProps } from "#ui/atoms/Icon" +import { variantCss } from "./badgeVariants" export interface BadgeProps - extends Omit, 'children'> { + extends Omit, "children"> { /** Render a different variant. */ variant: keyof typeof variantCss - icon?: IconProps['name'] + icon?: IconProps["name"] children: React.ReactNode } @@ -24,16 +24,16 @@ export const Badge: React.FC = ({ {...rest} className={cn([ className, - 'text-xs font-bold py-[3px] px-2 rounded inline-block', - variantCss[variant] + "text-xs font-bold py-[3px] px-2 rounded inline-block", + variantCss[variant], ])} > -
- {icon != null && } +
+ {icon != null && } {children}
) } -Badge.displayName = 'Badge' +Badge.displayName = "Badge" diff --git a/packages/app-elements/src/ui/atoms/Badge/badgeVariants.ts b/packages/app-elements/src/ui/atoms/Badge/badgeVariants.ts index 21b133876..b02e1bd49 100644 --- a/packages/app-elements/src/ui/atoms/Badge/badgeVariants.ts +++ b/packages/app-elements/src/ui/atoms/Badge/badgeVariants.ts @@ -1,28 +1,28 @@ type BadgeVariant = - | 'danger-solid' - | 'danger' - | 'primary-solid' - | 'primary' - | 'secondary-solid' - | 'secondary' - | 'success-solid' - | 'success' - | 'teal-solid' - | 'teal' - | 'warning-solid' - | 'warning' + | "danger-solid" + | "danger" + | "primary-solid" + | "primary" + | "secondary-solid" + | "secondary" + | "success-solid" + | "success" + | "teal-solid" + | "teal" + | "warning-solid" + | "warning" export const variantCss: Record = { - 'danger-solid': 'text-white bg-red', - 'primary-solid': 'text-white bg-primary', - 'secondary-solid': 'text-gray bg-gray-200', - 'success-solid': 'text-white bg-green', - 'teal-solid': 'text-white bg-teal-800', - 'warning-solid': 'text-white bg-orange', - danger: 'text-red bg-red-50', - primary: 'text-primary bg-primary-50', - secondary: 'text-gray-500 bg-gray-50', - success: 'text-green-600 bg-green-50', - teal: 'text-teal-800 bg-teal-50', - warning: 'text-orange-600 bg-orange-50' + "danger-solid": "text-white bg-red", + "primary-solid": "text-white bg-primary", + "secondary-solid": "text-gray bg-gray-200", + "success-solid": "text-white bg-green", + "teal-solid": "text-white bg-teal-800", + "warning-solid": "text-white bg-orange", + danger: "text-red bg-red-50", + primary: "text-primary bg-primary-50", + secondary: "text-gray-500 bg-gray-50", + success: "text-green-600 bg-green-50", + teal: "text-teal-800 bg-teal-50", + warning: "text-orange-600 bg-orange-50", } diff --git a/packages/app-elements/src/ui/atoms/Badge/index.ts b/packages/app-elements/src/ui/atoms/Badge/index.ts index 303418dbe..e9ffe5843 100644 --- a/packages/app-elements/src/ui/atoms/Badge/index.ts +++ b/packages/app-elements/src/ui/atoms/Badge/index.ts @@ -1 +1 @@ -export { Badge, type BadgeProps } from './Badge' +export { Badge, type BadgeProps } from "./Badge" diff --git a/packages/app-elements/src/ui/atoms/Button.test.tsx b/packages/app-elements/src/ui/atoms/Button.test.tsx index 1d9f035d2..201c54ab9 100644 --- a/packages/app-elements/src/ui/atoms/Button.test.tsx +++ b/packages/app-elements/src/ui/atoms/Button.test.tsx @@ -1,59 +1,59 @@ -import { render } from '@testing-library/react' -import { Button } from './Button' +import { render } from "@testing-library/react" +import { Button } from "./Button" -describe('Button', () => { - it('Should be rendered', () => { +describe("Button", () => { + it("Should be rendered", () => { const { getByRole } = render() - const button = getByRole('button') + const button = getByRole("button") expect(button).toBeVisible() - expect(button.innerHTML).toBe('click me') - expect(button.tagName).toBe('BUTTON') + expect(button.innerHTML).toBe("click me") + expect(button.tagName).toBe("BUTTON") expect(button).toBeInstanceOf(HTMLButtonElement) }) - it('Should render as primary variant', () => { + it("Should render as primary variant", () => { const { getByRole } = render() - expect(getByRole('button').className).toContain('bg-black') - expect(getByRole('button').className).toContain('text-white') + expect(getByRole("button").className).toContain("bg-black") + expect(getByRole("button").className).toContain("text-white") }) - it('Should render as link variant', () => { - const { getByRole } = render() - expect(getByRole('button').className).toContain('text-primary') + it("Should render as link variant", () => { + const { getByRole } = render() + expect(getByRole("button").className).toContain("text-primary") }) - it('Should render as secondary variant', () => { - const { getByRole } = render() - expect(getByRole('button').className).toContain('bg-white') - expect(getByRole('button').className).toContain('text-black') - expect(getByRole('button').className).toContain('border border-black') + it("Should render as secondary variant", () => { + const { getByRole } = render() + expect(getByRole("button").className).toContain("bg-white") + expect(getByRole("button").className).toContain("text-black") + expect(getByRole("button").className).toContain("border border-black") }) - it('Should render as danger variant', () => { - const { getByRole } = render() - expect(getByRole('button').className).toContain('bg-white') - expect(getByRole('button').className).toContain('text-red') - expect(getByRole('button').className).toContain('border border-red') + it("Should render as danger variant", () => { + const { getByRole } = render() + expect(getByRole("button").className).toContain("bg-white") + expect(getByRole("button").className).toContain("text-red") + expect(getByRole("button").className).toContain("border border-red") }) - it('Should render as size small', () => { - const { getByRole } = render() - expect(getByRole('button').className).toContain('px-4 py-2') + it("Should render as size small", () => { + const { getByRole } = render() + expect(getByRole("button").className).toContain("px-4 py-2") }) - it('Should render as size regular (default)', () => { + it("Should render as size regular (default)", () => { const { getByRole } = render() - expect(getByRole('button').className).toContain('px-6 py-3') + expect(getByRole("button").className).toContain("px-6 py-3") }) - it('Should render as size large', () => { - const { getByRole } = render() - expect(getByRole('button').className).toContain('px-8 py-4') + it("Should render as size large", () => { + const { getByRole } = render() + expect(getByRole("button").className).toContain("px-8 py-4") }) - it('Should render with flex alignment', () => { - const { getByRole } = render() - expect(getByRole('button').className).toContain('flex gap-1 items-center') + it("Should render with flex alignment", () => { + const { getByRole } = render() + expect(getByRole("button").className).toContain("flex gap-1 items-center") }) }) diff --git a/packages/app-elements/src/ui/atoms/Button.tsx b/packages/app-elements/src/ui/atoms/Button.tsx index 61df97101..bbc24042e 100644 --- a/packages/app-elements/src/ui/atoms/Button.tsx +++ b/packages/app-elements/src/ui/atoms/Button.tsx @@ -1,9 +1,9 @@ -import cn from 'classnames' -import { type JSX } from 'react' +import cn from "classnames" +import type { JSX } from "react" import { getInteractiveElementClassName, - type InteractiveElementProps -} from '../internals/InteractiveElement.className' + type InteractiveElementProps, +} from "../internals/InteractiveElement.className" export type ButtonProps = React.ButtonHTMLAttributes & InteractiveElementProps @@ -19,8 +19,8 @@ export function Button({ className, disabled, fullWidth, - size = 'regular', - variant = 'primary', + size = "regular", + variant = "primary", ...rest }: ButtonProps): JSX.Element { return ( @@ -33,8 +33,8 @@ export function Button({ disabled, fullWidth, size, - variant - }) + variant, + }), )} disabled={disabled} {...rest} @@ -44,4 +44,4 @@ export function Button({ ) } -Button.displayName = 'Button' +Button.displayName = "Button" diff --git a/packages/app-elements/src/ui/atoms/ButtonFilter.test.tsx b/packages/app-elements/src/ui/atoms/ButtonFilter.test.tsx index 4cee80460..79a801d28 100644 --- a/packages/app-elements/src/ui/atoms/ButtonFilter.test.tsx +++ b/packages/app-elements/src/ui/atoms/ButtonFilter.test.tsx @@ -1,52 +1,52 @@ -import { render } from '@testing-library/react' -import { ButtonFilter } from './ButtonFilter' +import { render } from "@testing-library/react" +import { ButtonFilter } from "./ButtonFilter" -describe('ButtonFilter', () => { - const mockedConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {}) +describe("ButtonFilter", () => { + const mockedConsoleLog = vi.spyOn(console, "log").mockImplementation(() => {}) afterEach(() => { vi.clearAllMocks() }) - test('Should be rendered', () => { - const { container } = render() + test("Should be rendered", () => { + const { container } = render() expect(container).toBeVisible() }) - test('Should render only the main button', () => { + test("Should render only the main button", () => { const { getByTestId, queryByTestId } = render( { - console.log('main-button-clicked') + console.log("main-button-clicked") }} - /> + />, ) - expect(getByTestId('ButtonFilter-main')).toBeVisible() - expect(queryByTestId('ButtonFilter-remove')).toBe(null) - getByTestId('ButtonFilter-main').click() - expect(mockedConsoleLog).toHaveBeenCalledWith('main-button-clicked') + expect(getByTestId("ButtonFilter-main")).toBeVisible() + expect(queryByTestId("ButtonFilter-remove")).toBe(null) + getByTestId("ButtonFilter-main").click() + expect(mockedConsoleLog).toHaveBeenCalledWith("main-button-clicked") }) - test('Should render optional remove button ', () => { + test("Should render optional remove button ", () => { const { getByTestId } = render( { - console.log('remove-request') + console.log("remove-request") }} - /> + />, ) - expect(getByTestId('ButtonFilter-main')).toBeVisible() - expect(getByTestId('ButtonFilter-remove')).toBeVisible() - getByTestId('ButtonFilter-remove').click() - expect(mockedConsoleLog).toHaveBeenCalledWith('remove-request') + expect(getByTestId("ButtonFilter-main")).toBeVisible() + expect(getByTestId("ButtonFilter-remove")).toBeVisible() + getByTestId("ButtonFilter-remove").click() + expect(mockedConsoleLog).toHaveBeenCalledWith("remove-request") }) - test('Should render left icon when passed as prop', () => { + test("Should render left icon when passed as prop", () => { const { getByTestId } = render( - + , ) - expect(getByTestId('ButtonFilter-icon')).toBeVisible() + expect(getByTestId("ButtonFilter-icon")).toBeVisible() }) }) diff --git a/packages/app-elements/src/ui/atoms/ButtonFilter.tsx b/packages/app-elements/src/ui/atoms/ButtonFilter.tsx index c20e467bc..fa64ab032 100644 --- a/packages/app-elements/src/ui/atoms/ButtonFilter.tsx +++ b/packages/app-elements/src/ui/atoms/ButtonFilter.tsx @@ -1,12 +1,12 @@ -import cn from 'classnames' -import { type JSX } from 'react' -import { StatusIcon, type StatusIconProps } from './StatusIcon' +import cn from "classnames" +import type { JSX } from "react" +import { StatusIcon, type StatusIconProps } from "./StatusIcon" export interface ButtonFilterProps extends React.HTMLAttributes { onClick?: () => void onRemoveRequest?: () => void - icon?: StatusIconProps['name'] + icon?: StatusIconProps["name"] label: string } @@ -21,49 +21,49 @@ function ButtonFilter({ return (
{onRemoveRequest != null ? ( ) : null}
) } -ButtonFilter.displayName = 'ButtonFilter' +ButtonFilter.displayName = "ButtonFilter" export { ButtonFilter } diff --git a/packages/app-elements/src/ui/atoms/ButtonImageSelect.test.tsx b/packages/app-elements/src/ui/atoms/ButtonImageSelect.test.tsx index 29cf74c67..f6dc8ba47 100644 --- a/packages/app-elements/src/ui/atoms/ButtonImageSelect.test.tsx +++ b/packages/app-elements/src/ui/atoms/ButtonImageSelect.test.tsx @@ -1,34 +1,34 @@ -import { render } from '@testing-library/react' -import { ButtonImageSelect } from './ButtonImageSelect' +import { render } from "@testing-library/react" +import { ButtonImageSelect } from "./ButtonImageSelect" -describe('ButtonImageSelect', () => { - const mockedConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {}) +describe("ButtonImageSelect", () => { + const mockedConsoleLog = vi.spyOn(console, "log").mockImplementation(() => {}) afterEach(() => { vi.clearAllMocks() }) - test('Should be rendered', () => { + test("Should be rendered", () => { const { container } = render() expect(container).toBeVisible() }) - test('Should render with image', () => { + test("Should render with image", () => { const { getByTestId } = render( { - console.log('main-button-clicked') + console.log("main-button-clicked") }} - /> + />, ) - expect(getByTestId('ButtonImageSelect-main')).toContainElement( - getByTestId('ButtonImageSelect-image') + expect(getByTestId("ButtonImageSelect-main")).toContainElement( + getByTestId("ButtonImageSelect-image"), ) - getByTestId('ButtonImageSelect-main').click() - expect(mockedConsoleLog).toHaveBeenCalledWith('main-button-clicked') + getByTestId("ButtonImageSelect-main").click() + expect(mockedConsoleLog).toHaveBeenCalledWith("main-button-clicked") }) }) diff --git a/packages/app-elements/src/ui/atoms/ButtonImageSelect.tsx b/packages/app-elements/src/ui/atoms/ButtonImageSelect.tsx index 2a7bc8c65..2d97143c9 100644 --- a/packages/app-elements/src/ui/atoms/ButtonImageSelect.tsx +++ b/packages/app-elements/src/ui/atoms/ButtonImageSelect.tsx @@ -1,6 +1,6 @@ -import cn from 'classnames' -import { type JSX } from 'react' -import { Icon } from './Icon' +import cn from "classnames" +import type { JSX } from "react" +import { Icon } from "./Icon" interface ButtonImage { src: string @@ -21,13 +21,13 @@ export function ButtonImageSelect({ }: ButtonImageSelectProps): JSX.Element { return ( ) } -ButtonImageSelect.displayName = 'ButtonImageSelect' +ButtonImageSelect.displayName = "ButtonImageSelect" diff --git a/packages/app-elements/src/ui/atoms/Card.test.tsx b/packages/app-elements/src/ui/atoms/Card.test.tsx index 69e3a626a..d26b31947 100644 --- a/packages/app-elements/src/ui/atoms/Card.test.tsx +++ b/packages/app-elements/src/ui/atoms/Card.test.tsx @@ -1,76 +1,76 @@ -import { render } from '@testing-library/react' -import { Card } from './Card' +import { render } from "@testing-library/react" +import { Card } from "./Card" -describe('Card', () => { - test('Should be rendered simply as DIV', () => { +describe("Card", () => { + test("Should be rendered simply as DIV", () => { const { getByText, container } = render( - +

I am a Card

-
+
, ) - expect(getByText('I am a Card')).toBeVisible() - expect(container.firstElementChild?.tagName).toBe('DIV') + expect(getByText("I am a Card")).toBeVisible() + expect(container.firstElementChild?.tagName).toBe("DIV") }) - test('Should be rendered as BUTTON when `onClick` is set', () => { + test("Should be rendered as BUTTON when `onClick` is set", () => { const { getByText, container } = render( - {}}> + {}}>

I am a Card

-
+
, ) - expect(getByText('I am a Card')).toBeVisible() - expect(container.firstElementChild?.tagName).toBe('BUTTON') + expect(getByText("I am a Card")).toBeVisible() + expect(container.firstElementChild?.tagName).toBe("BUTTON") }) - test('Should be rendered as BUTTON when `onClick` is set', () => { + test("Should be rendered as BUTTON when `onClick` is set", () => { const { getByText, container } = render( - {}}> + {}}>

I am a Card

-
+
, ) - expect(getByText('I am a Card')).toBeVisible() - expect(container.firstElementChild?.tagName).toBe('A') - expect(container.firstElementChild?.getAttribute('href')).toBe( - 'https://example.com' + expect(getByText("I am a Card")).toBeVisible() + expect(container.firstElementChild?.tagName).toBe("A") + expect(container.firstElementChild?.getAttribute("href")).toBe( + "https://example.com", ) }) - test('Should have light gray background', () => { + test("Should have light gray background", () => { const { container } = render( - + I am a Card - + , ) - expect(container.firstElementChild).toHaveClass('bg-gray-50') + expect(container.firstElementChild).toHaveClass("bg-gray-50") }) - test('Should have overflow hidden', () => { + test("Should have overflow hidden", () => { const { container } = render( - + I am a Card - + , ) const mainDiv = container.firstElementChild const innerDiv = container.firstElementChild?.firstElementChild - expect(mainDiv).toHaveClass('overflow-hidden') - expect(innerDiv).toHaveClass('overflow-hidden') + expect(mainDiv).toHaveClass("overflow-hidden") + expect(innerDiv).toHaveClass("overflow-hidden") }) - test('Should NOT have overflow hidden', () => { + test("Should NOT have overflow hidden", () => { const { container } = render( - + I am a Card - + , ) const mainDiv = container.firstElementChild const innerDiv = container.firstElementChild?.firstElementChild - expect(mainDiv).not.toHaveClass('overflow-hidden') - expect(innerDiv).not.toHaveClass('overflow-hidden') + expect(mainDiv).not.toHaveClass("overflow-hidden") + expect(innerDiv).not.toHaveClass("overflow-hidden") }) }) diff --git a/packages/app-elements/src/ui/atoms/Card.tsx b/packages/app-elements/src/ui/atoms/Card.tsx index 51654f03b..1ce99a461 100644 --- a/packages/app-elements/src/ui/atoms/Card.tsx +++ b/packages/app-elements/src/ui/atoms/Card.tsx @@ -1,9 +1,9 @@ -import { removeUnwantedProps } from '#utils/htmltags' -import cn from 'classnames' -import { withSkeletonTemplate } from './SkeletonTemplate' +import cn from "classnames" +import { removeUnwantedProps } from "#utils/htmltags" +import { withSkeletonTemplate } from "./SkeletonTemplate" export type CardProps = React.HTMLAttributes & - Pick, 'onClick' | 'href'> & { + Pick, "onClick" | "href"> & { /** * Footer will render in a dedicated section below the main content. */ @@ -11,7 +11,7 @@ export type CardProps = React.HTMLAttributes & /** * Set a gray background color */ - backgroundColor?: 'light' + backgroundColor?: "light" } & ( | { /** @@ -22,19 +22,19 @@ export type CardProps = React.HTMLAttributes & * * @default 6 */ - gap?: '1' | '4' | '6' + gap?: "1" | "4" | "6" /** * Set the overflow behavior. In most of the cases you might want to keep overflow visible, * but when you have inner content with hover effects you might want to set overflow to hidden. */ - overflow: 'visible' | 'hidden' + overflow: "visible" | "hidden" } | { /** * When card is rendered with no gap, overflow is always intended as hidden and cannot be controlled via props, * otherwise inner content will overlap the rounded corners of the card. */ - gap: 'none' + gap: "none" } ) @@ -43,44 +43,44 @@ export const Card = withSkeletonTemplate( ({ className, children, - gap = '6', + gap = "6", isLoading, delayMs, footer, backgroundColor, ...rest }) => { - const overflow = 'overflow' in rest ? rest.overflow : 'hidden' + const overflow = "overflow" in rest ? rest.overflow : "hidden" const divProps = - 'overflow' in rest ? removeUnwantedProps(rest, ['overflow']) : rest + "overflow" in rest ? removeUnwantedProps(rest, ["overflow"]) : rest const JsxTag = - rest.href != null ? 'a' : rest.onClick != null ? 'button' : 'div' + rest.href != null ? "a" : rest.onClick != null ? "button" : "div" return ( - 'text-inherit active:text-inherit hover:text-inherit font-inherit', // reset + "border border-solid rounded-md", + "text-left", // reset @@ -84,11 +82,11 @@ export const CodeBlock = withSkeletonTemplate(
) - } + }, ) -CodeBlock.displayName = 'CodeBlock' +CodeBlock.displayName = "CodeBlock" function randomHiddenValue(): string { - return '*'.repeat(Math.floor(Math.random() * 7) + 10) + return "*".repeat(Math.floor(Math.random() * 7) + 10) } diff --git a/packages/app-elements/src/ui/atoms/Container.test.tsx b/packages/app-elements/src/ui/atoms/Container.test.tsx index e7624648e..000f5f620 100644 --- a/packages/app-elements/src/ui/atoms/Container.test.tsx +++ b/packages/app-elements/src/ui/atoms/Container.test.tsx @@ -1,5 +1,5 @@ -import { render, type RenderResult } from '@testing-library/react' -import { Container } from './Container' +import { type RenderResult, render } from "@testing-library/react" +import { Container } from "./Container" interface SetupProps { id: string @@ -13,19 +13,19 @@ const setup = ({ id }: SetupProps): SetupResult => { const utils = render(
my app
-
+ , ) const element = utils.getByTestId(id) return { element, - ...utils + ...utils, } } -describe('Container', () => { - test('Should be rendered', () => { - const { element } = setup({ id: 'my-container' }) - expect(element.tagName).toBe('DIV') - expect(element.className).toContain('container') +describe("Container", () => { + test("Should be rendered", () => { + const { element } = setup({ id: "my-container" }) + expect(element.tagName).toBe("DIV") + expect(element.className).toContain("container") }) }) diff --git a/packages/app-elements/src/ui/atoms/Container.tsx b/packages/app-elements/src/ui/atoms/Container.tsx index 5cd9142c3..cf991b900 100644 --- a/packages/app-elements/src/ui/atoms/Container.tsx +++ b/packages/app-elements/src/ui/atoms/Container.tsx @@ -1,4 +1,4 @@ -import cn from 'classnames' +import cn from "classnames" export interface ContainerProps { /** @@ -25,9 +25,9 @@ export const Container: React.FC = ({ return (
@@ -36,4 +36,4 @@ export const Container: React.FC = ({ ) } -Container.displayName = 'Container' +Container.displayName = "Container" diff --git a/packages/app-elements/src/ui/atoms/CopyToClipboard.test.tsx b/packages/app-elements/src/ui/atoms/CopyToClipboard.test.tsx index c6cd2ee40..73b83a831 100644 --- a/packages/app-elements/src/ui/atoms/CopyToClipboard.test.tsx +++ b/packages/app-elements/src/ui/atoms/CopyToClipboard.test.tsx @@ -1,10 +1,10 @@ import { fireEvent, + type RenderResult, render, waitFor, - type RenderResult -} from '@testing-library/react' -import { CopyToClipboard } from './CopyToClipboard' +} from "@testing-library/react" +import { CopyToClipboard } from "./CopyToClipboard" interface SetupProps { id: string @@ -20,46 +20,46 @@ const setup = ({ id, value }: SetupProps): SetupResult => { const element = utils.getByTestId(id) return { element, - ...utils + ...utils, } } -describe('CopyToClipboard', () => { - test('Should be rendered with copy button', () => { +describe("CopyToClipboard", () => { + test("Should be rendered with copy button", () => { const { element, getByText, getByTestId } = setup({ - id: 'my-value', - value: 'ABCD1234' + id: "my-value", + value: "ABCD1234", }) expect(element).toBeInTheDocument() - expect(getByText('ABCD1234')).toBeInTheDocument() - expect(getByTestId('copy-value-button')).toBeInTheDocument() + expect(getByText("ABCD1234")).toBeInTheDocument() + expect(getByTestId("copy-value-button")).toBeInTheDocument() }) - test('Should display a dash and no button when value is empty or undefined', () => { + test("Should display a dash and no button when value is empty or undefined", () => { const { element, getByTestId } = setup({ - id: 'my-value', - value: '' + id: "my-value", + value: "", }) expect(element).toBeInTheDocument() - expect(getByTestId('empty-string')).toBeInTheDocument() + expect(getByTestId("empty-string")).toBeInTheDocument() expect(element.querySelector('[data-testid="copy-value-button"]')).toBe( - null + null, ) }) }) -describe('CopyToClipboard click', () => { +describe("CopyToClipboard click", () => { // mocking clipboard const initialClipboard = { ...global.navigator.clipboard } beforeEach(() => { - let clipboardValue = '' + let clipboardValue = "" ;(global.navigator as any).clipboard = { writeText: vi.fn((text: string) => { clipboardValue = text }), readText: vi.fn(() => { return clipboardValue - }) + }), } }) @@ -68,21 +68,19 @@ describe('CopyToClipboard click', () => { ;(global.navigator as any) = initialClipboard }) - test('Should copy text into clipboard', async () => { - const value = 'BEANIEXXFFFFFF000000XXXX' + test("Should copy text into clipboard", async () => { + const value = "BEANIEXXFFFFFF000000XXXX" const { container, getByTestId } = render( - + , ) expect(container).toBeInTheDocument() - expect(navigator.clipboard.readText()).toBe('') + expect(navigator.clipboard.readText()).toBe("") - fireEvent.click(getByTestId('copy-value-button')) + fireEvent.click(getByTestId("copy-value-button")) await waitFor(() => { - // eslint-disable-next-line @typescript-eslint/unbound-method expect(navigator.clipboard.writeText).toBeCalledTimes(1) - // eslint-disable-next-line @typescript-eslint/unbound-method expect(navigator.clipboard.writeText).toHaveBeenCalledWith(value) expect(navigator.clipboard.readText()).toBe(value) }) diff --git a/packages/app-elements/src/ui/atoms/CopyToClipboard.tsx b/packages/app-elements/src/ui/atoms/CopyToClipboard.tsx index 08b3e69bd..e91e010ee 100644 --- a/packages/app-elements/src/ui/atoms/CopyToClipboard.tsx +++ b/packages/app-elements/src/ui/atoms/CopyToClipboard.tsx @@ -1,9 +1,9 @@ -import { Text } from '#ui/atoms/Text' -import { Check, Copy } from '@phosphor-icons/react' -import cn from 'classnames' -import isEmpty from 'lodash-es/isEmpty' -import { useCallback, useEffect, useState } from 'react' -import invariant from 'ts-invariant' +import { CheckIcon, CopyIcon } from "@phosphor-icons/react" +import cn from "classnames" +import isEmpty from "lodash-es/isEmpty" +import { useCallback, useEffect, useState } from "react" +import invariant from "ts-invariant" +import { Text } from "#ui/atoms/Text" export interface CopyToClipboardProps { /** @@ -34,7 +34,7 @@ export const CopyToClipboard: React.FC = ({ invariant( feedbackDurationMs > transitionMs, - 'feedbackDurationMs must be greater than transitionMs' + "feedbackDurationMs must be greater than transitionMs", ) const handleCopy = useCallback( @@ -42,7 +42,7 @@ export const CopyToClipboard: React.FC = ({ await navigator.clipboard.writeText(v) setCopied(true) }, - [value] + [value], ) useEffect( @@ -55,19 +55,19 @@ export const CopyToClipboard: React.FC = ({ setCopied(false) }, feedbackDurationMs + transitionMs) }, - [copied] + [copied], ) if (value == null || isEmpty(value)) { return (
- +
@@ -77,15 +77,15 @@ export const CopyToClipboard: React.FC = ({ return (
{showValue && ( -
+
{isJsonString(value) ? ( -
+
{JSON.stringify(JSON.parse(value), null, 2)}
) : ( @@ -94,52 +94,52 @@ export const CopyToClipboard: React.FC = ({
)} @@ -151,10 +151,10 @@ export const CopyToClipboard: React.FC = ({ function isJsonString(str: string): boolean { try { JSON.parse(str) - } catch (e) { + } catch (_e) { return false } return true } -CopyToClipboard.displayName = 'CopyToClipboard' +CopyToClipboard.displayName = "CopyToClipboard" diff --git a/packages/app-elements/src/ui/atoms/EmptyState.test.tsx b/packages/app-elements/src/ui/atoms/EmptyState.test.tsx index c0642214f..11368b1e9 100644 --- a/packages/app-elements/src/ui/atoms/EmptyState.test.tsx +++ b/packages/app-elements/src/ui/atoms/EmptyState.test.tsx @@ -1,5 +1,5 @@ -import { render, type RenderResult } from '@testing-library/react' -import { EmptyState } from './EmptyState' +import { type RenderResult, render } from "@testing-library/react" +import { EmptyState } from "./EmptyState" interface SetupProps { id: string @@ -16,28 +16,28 @@ const setup = ({ id, ...rest }: SetupProps): SetupResult => { const element = utils.getByTestId(id) return { element, - ...utils + ...utils, } } -describe('EmptyState', () => { - test('Should be rendered', () => { +describe("EmptyState", () => { + test("Should be rendered", () => { const { element, getByText } = setup({ - id: 'my-element', - title: 'Your list is empty' + id: "my-element", + title: "Your list is empty", }) expect(element).toBeInTheDocument() - expect(getByText('Your list is empty')).toBeInTheDocument() + expect(getByText("Your list is empty")).toBeInTheDocument() }) - test('Should render optional description ', () => { + test("Should render optional description ", () => { const { element, getByText } = setup({ - id: 'my-element', - title: 'Your list is empty', - description: 'Lorem impsum' + id: "my-element", + title: "Your list is empty", + description: "Lorem impsum", }) expect(element).toBeInTheDocument() - expect(getByText('Your list is empty')).toBeInTheDocument() - expect(getByText('Lorem impsum')).toBeInTheDocument() + expect(getByText("Your list is empty")).toBeInTheDocument() + expect(getByText("Lorem impsum")).toBeInTheDocument() }) }) diff --git a/packages/app-elements/src/ui/atoms/EmptyState.tsx b/packages/app-elements/src/ui/atoms/EmptyState.tsx index 1ced1fa91..a73d1af17 100644 --- a/packages/app-elements/src/ui/atoms/EmptyState.tsx +++ b/packages/app-elements/src/ui/atoms/EmptyState.tsx @@ -1,13 +1,13 @@ -import cn from 'classnames' -import { type JSX, type ReactNode } from 'react' -import { StatusIcon, type StatusIconProps } from './StatusIcon' +import cn from "classnames" +import type { JSX, ReactNode } from "react" +import { StatusIcon, type StatusIconProps } from "./StatusIcon" export interface EmptyStateProps { title: string description?: ReactNode action?: ReactNode className?: string - icon?: StatusIconProps['name'] + icon?: StatusIconProps["name"] } function EmptyState({ @@ -21,26 +21,26 @@ function EmptyState({ return (
-
-
-

{title}

+
+
+

{title}

{description != null ? ( -
+
{description}
) : null} - {action != null ?
{action}
: null} + {action != null ?
{action}
: null}
-
+
{icon != null && ( -
+
)} @@ -50,5 +50,5 @@ function EmptyState({ ) } -EmptyState.displayName = 'EmptyState' +EmptyState.displayName = "EmptyState" export { EmptyState } diff --git a/packages/app-elements/src/ui/atoms/Grid.test.tsx b/packages/app-elements/src/ui/atoms/Grid.test.tsx index befa3dacb..ca79a35fc 100644 --- a/packages/app-elements/src/ui/atoms/Grid.test.tsx +++ b/packages/app-elements/src/ui/atoms/Grid.test.tsx @@ -1,15 +1,15 @@ -import { render } from '@testing-library/react' -import { Grid } from './Grid' +import { render } from "@testing-library/react" +import { Grid } from "./Grid" -describe('Grid', () => { - test('renders', () => { +describe("Grid", () => { + test("renders", () => { const { container } = render( - +
item1
item2
item3
item4
-
+
, ) expect(container).toBeVisible() diff --git a/packages/app-elements/src/ui/atoms/Grid.tsx b/packages/app-elements/src/ui/atoms/Grid.tsx index 505417bc3..7d4c32675 100644 --- a/packages/app-elements/src/ui/atoms/Grid.tsx +++ b/packages/app-elements/src/ui/atoms/Grid.tsx @@ -1,21 +1,21 @@ -import cn from 'classnames' -import React, { type JSX, type ReactNode } from 'react' +import cn from "classnames" +import type React from "react" export interface GridProps - extends Omit, 'children'> { + extends Omit, "children"> { /** * Grid items */ - children: ReactNode + children: React.ReactNode /** * Number of columns */ - columns?: '1' | '2' | 'auto' + columns?: "1" | "2" | "auto" /** * Items alignment. * When not set all items will hame same height (items-stretch) */ - alignItems?: 'center' | 'start' | 'end' + alignItems?: "center" | "start" | "end" } /** @@ -29,15 +29,15 @@ function Grid({ className, alignItems, ...rest -}: GridProps): JSX.Element { +}: GridProps): React.JSX.Element { return (
@@ -46,5 +46,5 @@ function Grid({ ) } -Grid.displayName = 'Grid' +Grid.displayName = "Grid" export { Grid } diff --git a/packages/app-elements/src/ui/atoms/Hint.test.tsx b/packages/app-elements/src/ui/atoms/Hint.test.tsx index 80df70754..45818ca50 100644 --- a/packages/app-elements/src/ui/atoms/Hint.test.tsx +++ b/packages/app-elements/src/ui/atoms/Hint.test.tsx @@ -1,7 +1,7 @@ -import { render, type RenderResult } from '@testing-library/react' -import { Hint, type HintProps } from './Hint' +import { type RenderResult, render } from "@testing-library/react" +import { Hint, type HintProps } from "./Hint" -interface SetupProps extends Omit { +interface SetupProps extends Omit { id: string } @@ -13,27 +13,27 @@ const setup = ({ id, ...rest }: SetupProps): SetupResult => { const utils = render( This is an helper text. - + , ) const element = utils.getByTestId(id) as HTMLInputElement return { element, - ...utils + ...utils, } } -describe('Hint', () => { - test('Should be rendered', () => { - const { element } = setup({ id: 'my-helper-text' }) +describe("Hint", () => { + test("Should be rendered", () => { + const { element } = setup({ id: "my-helper-text" }) expect(element).toBeInTheDocument() }) - test('Should show an icon', () => { + test("Should show an icon", () => { const { element } = setup({ - id: 'my-helper-text-with-bulb', - icon: 'lightbulbFilament' + id: "my-helper-text-with-bulb", + icon: "lightbulbFilament", }) expect(element).toBeInTheDocument() - expect(element.querySelector('svg')).toBeInTheDocument() + expect(element.querySelector("svg")).toBeInTheDocument() }) }) diff --git a/packages/app-elements/src/ui/atoms/Hint.tsx b/packages/app-elements/src/ui/atoms/Hint.tsx index 67aac2483..1d4ec7166 100644 --- a/packages/app-elements/src/ui/atoms/Hint.tsx +++ b/packages/app-elements/src/ui/atoms/Hint.tsx @@ -1,9 +1,9 @@ -import cn from 'classnames' -import { type JSX, type ReactNode } from 'react' -import { Icon, type IconProps } from './Icon' +import cn from "classnames" +import type { JSX, ReactNode } from "react" +import { Icon, type IconProps } from "./Icon" export interface HintProps { - icon?: IconProps['name'] + icon?: IconProps["name"] className?: string children: ReactNode } @@ -15,11 +15,11 @@ export function Hint({ ...rest }: HintProps): JSX.Element { return ( -
+
{icon != null && } -
{children}
+
{children}
) } -Hint.displayName = 'Hint' +Hint.displayName = "Hint" diff --git a/packages/app-elements/src/ui/atoms/Hr.test.tsx b/packages/app-elements/src/ui/atoms/Hr.test.tsx index d6a0c1679..d619a8b30 100644 --- a/packages/app-elements/src/ui/atoms/Hr.test.tsx +++ b/packages/app-elements/src/ui/atoms/Hr.test.tsx @@ -1,15 +1,15 @@ -import { render } from '@testing-library/react' -import { Hr } from './Hr' +import { render } from "@testing-library/react" +import { Hr } from "./Hr" -describe('Hr', () => { - test('Should be rendered', () => { - const { getByTestId } = render(
) - const element = getByTestId('hr') +describe("Hr", () => { + test("Should be rendered", () => { + const { getByTestId } = render(
) + const element = getByTestId("hr") expect(element).toBeVisible() - expect(element.tagName).toBe('HR') + expect(element.tagName).toBe("HR") expect(Array.from(element.classList)).toEqual([ - 'border-t', - 'border-gray-100' + "border-t", + "border-gray-100", ]) }) }) diff --git a/packages/app-elements/src/ui/atoms/Hr.tsx b/packages/app-elements/src/ui/atoms/Hr.tsx index 1e0e45483..6224cd8fb 100644 --- a/packages/app-elements/src/ui/atoms/Hr.tsx +++ b/packages/app-elements/src/ui/atoms/Hr.tsx @@ -1,11 +1,11 @@ -import cn from 'classnames' +import cn from "classnames" -export type HrProps = Omit, 'children'> & { +export type HrProps = Omit, "children"> & { /** * The variant of the horizontal rule. * @default solid */ - variant?: 'solid' | 'dashed' + variant?: "solid" | "dashed" } /** @@ -16,15 +16,15 @@ export const Hr: React.FC = ({ className, variant, ...rest }) => { return (
) } -Hr.displayName = 'Hr' +Hr.displayName = "Hr" diff --git a/packages/app-elements/src/ui/atoms/Icon/Icon.test.tsx b/packages/app-elements/src/ui/atoms/Icon/Icon.test.tsx index f52e994ff..50b2fccd4 100644 --- a/packages/app-elements/src/ui/atoms/Icon/Icon.test.tsx +++ b/packages/app-elements/src/ui/atoms/Icon/Icon.test.tsx @@ -1,21 +1,21 @@ -import { render, type RenderResult } from '@testing-library/react' -import { Icon } from './Icon' +import { type RenderResult, render } from "@testing-library/react" +import { Icon } from "./Icon" type SetupResult = RenderResult & { element: HTMLElement } const setup = (): SetupResult => { - const utils = render() - const element = utils.getByTestId('my-icon') + const utils = render() + const element = utils.getByTestId("my-icon") return { element, - ...utils + ...utils, } } -describe('Icon', () => { - test('Should be rendered', () => { +describe("Icon", () => { + test("Should be rendered", () => { const { element } = setup() expect(element).toBeVisible() }) diff --git a/packages/app-elements/src/ui/atoms/Icon/Icon.tsx b/packages/app-elements/src/ui/atoms/Icon/Icon.tsx index ea4d83a4a..068575a97 100644 --- a/packages/app-elements/src/ui/atoms/Icon/Icon.tsx +++ b/packages/app-elements/src/ui/atoms/Icon/Icon.tsx @@ -1,9 +1,9 @@ -import { useMemo, type ComponentPropsWithRef } from 'react' -import { iconMapping } from './icons' +import { type ComponentPropsWithRef, useMemo } from "react" +import { iconMapping } from "./icons" -type IconWeight = 'regular' | 'bold' | 'light' | 'fill' +type IconWeight = "regular" | "bold" | "light" | "fill" -export interface IconProps extends ComponentPropsWithRef<'svg'> { +export interface IconProps extends ComponentPropsWithRef<"svg"> { /** * Name of the icon to display */ @@ -25,4 +25,4 @@ export const Icon: React.FC = ({ name, ...rest }) => { return } -Icon.displayName = 'Icon' +Icon.displayName = "Icon" diff --git a/packages/app-elements/src/ui/atoms/Icon/icons.tsx b/packages/app-elements/src/ui/atoms/Icon/icons.tsx index a80980445..423033d9e 100644 --- a/packages/app-elements/src/ui/atoms/Icon/icons.tsx +++ b/packages/app-elements/src/ui/atoms/Icon/icons.tsx @@ -1,135 +1,135 @@ -import * as phosphor from '@phosphor-icons/react' +import * as phosphor from "@phosphor-icons/react" export const iconMapping = { - airplaneTakeoff: phosphor.AirplaneTakeoff, - appWindow: phosphor.AppWindow, - archive: phosphor.Archive, - arrowBendDownRight: phosphor.ArrowBendDownRight, - arrowCircleDown: phosphor.ArrowCircleDown, - arrowCircleUp: phosphor.ArrowCircleUp, - arrowCircleUpRight: phosphor.ArrowCircleUpRight, - arrowClockwise: phosphor.ArrowClockwise, - arrowDown: phosphor.ArrowDown, - arrowLeft: phosphor.ArrowLeft, - arrowRight: phosphor.ArrowRight, - arrowsDownUp: phosphor.ArrowsDownUp, - arrowsHorizontal: phosphor.ArrowsHorizontal, - arrowsLeftRight: phosphor.ArrowsLeftRight, - arrowSquareOut: phosphor.ArrowSquareOut, - arrowUDownLeft: phosphor.ArrowUDownLeft, - arrowUp: phosphor.ArrowUp, - arrowUpRight: phosphor.ArrowUpRight, - arrowUUpLeft: phosphor.ArrowUUpLeft, - asteriskSimple: phosphor.AsteriskSimple, - bank: phosphor.Bank, - bookOpenText: phosphor.BookOpenText, - bracketsCurly: phosphor.BracketsCurly, - buildings: phosphor.Buildings, - calculator: phosphor.Calculator, - calendarBlank: phosphor.CalendarBlank, - calendarCheck: phosphor.CalendarCheck, - calendarDots: phosphor.CalendarDots, - camera: phosphor.Camera, - caretDown: phosphor.CaretDown, - caretRight: phosphor.CaretRight, - chat: phosphor.Chat, - chatCircle: phosphor.ChatCircle, - check: phosphor.Check, - checkSquareOffset: phosphor.CheckSquareOffset, - clipboardText: phosphor.ClipboardText, - clockClockwise: phosphor.ClockClockwise, - cloudArrowUp: phosphor.CloudArrowUp, - copy: phosphor.Copy, - creditCard: phosphor.CreditCard, - currencyEur: phosphor.CurrencyEur, - diamondsFour: phosphor.DiamondsFour, - discord: phosphor.DiscordLogo, - dotsSixVertical: phosphor.DotsSixVertical, - dotsThree: phosphor.DotsThree, - dotsThreeVertical: phosphor.DotsThreeVertical, - download: phosphor.Download, - envelopeSimple: phosphor.EnvelopeSimple, - equals: phosphor.Equals, - eye: phosphor.Eye, - eyeSlash: phosphor.EyeSlash, - flag: phosphor.Flag, - folderOpen: phosphor.FolderOpen, - funnelSimple: phosphor.FunnelSimple, - gear: phosphor.Gear, - gearFine: phosphor.GearFine, - gift: phosphor.Gift, - gitFork: phosphor.GitFork, - githubLogo: phosphor.GithubLogo, - globe: phosphor.Globe, - globeSimple: phosphor.GlobeSimple, - googleLogo: phosphor.GoogleLogo, - hourglass: phosphor.Hourglass, - houseSimple: phosphor.HouseSimple, - info: phosphor.Info, - key: phosphor.Key, - lifebuoy: phosphor.Lifebuoy, - lightbulbFilament: phosphor.LightbulbFilament, - lightning: phosphor.Lightning, - link: phosphor.Link, - linkSimple: phosphor.LinkSimple, - list: phosphor.List, - lockSimple: phosphor.LockSimple, - lockSimpleOpen: phosphor.LockSimpleOpen, - magnifyingGlass: phosphor.MagnifyingGlass, - mapPin: phosphor.MapPin, - megaphoneSimple: phosphor.MegaphoneSimple, - minus: phosphor.Minus, - minusCircle: phosphor.MinusCircle, - package: phosphor.Package, - pencilSimple: phosphor.PencilSimple, - percent: phosphor.Percent, - plus: phosphor.Plus, - plusCircle: phosphor.PlusCircle, - printer: phosphor.Printer, - pulse: phosphor.Pulse, - pushPin: phosphor.PushPin, - puzzlePiece: phosphor.PuzzlePiece, - qrCode: phosphor.QrCode, - question: phosphor.Question, - receipt: phosphor.Receipt, - rocketLaunch: phosphor.RocketLaunch, - seal: phosphor.Seal, - sealPercent: phosphor.SealPercent, - shapes: phosphor.Shapes, - shareFat: phosphor.ShareFat, - shield: phosphor.Shield, - shoppingBag: phosphor.ShoppingBag, - shoppingBagOpen: phosphor.ShoppingBagOpen, - sidebarSimple: phosphor.SidebarSimple, - signOut: phosphor.SignOut, - slackLogo: phosphor.SlackLogo, - sliders: phosphor.Sliders, - squaresFour: phosphor.SquaresFour, - stack: phosphor.Stack, - star: phosphor.Star, - storefront: phosphor.Storefront, - suitcaseSimple: phosphor.SuitcaseSimple, - tag: phosphor.Tag, - target: phosphor.Target, - ticket: phosphor.Ticket, - trash: phosphor.Trash, - treeStructure: phosphor.TreeStructure, - treeView: phosphor.TreeView, - trendUp: phosphor.TrendUp, - truck: phosphor.Truck, - tShirt: phosphor.TShirt, - upload: phosphor.Upload, - user: phosphor.User, - userCircle: phosphor.UserCircle, - userRectangle: phosphor.UserRectangle, - users: phosphor.Users, - usersThree: phosphor.UsersThree, - warehouse: phosphor.Warehouse, - warning: phosphor.Warning, - warningCircle: phosphor.WarningCircle, - webhooksLogo: phosphor.WebhooksLogo, - whatsappLogo: phosphor.WhatsappLogo, - wrench: phosphor.Wrench, - x: phosphor.X, - xCircle: phosphor.XCircle + airplaneTakeoff: phosphor.AirplaneTakeoffIcon, + appWindow: phosphor.AppWindowIcon, + archive: phosphor.ArchiveIcon, + arrowBendDownRight: phosphor.ArrowBendDownRightIcon, + arrowCircleDown: phosphor.ArrowCircleDownIcon, + arrowCircleUp: phosphor.ArrowCircleUpIcon, + arrowCircleUpRight: phosphor.ArrowCircleUpRightIcon, + arrowClockwise: phosphor.ArrowClockwiseIcon, + arrowDown: phosphor.ArrowDownIcon, + arrowLeft: phosphor.ArrowLeftIcon, + arrowRight: phosphor.ArrowRightIcon, + arrowsDownUp: phosphor.ArrowsDownUpIcon, + arrowsHorizontal: phosphor.ArrowsHorizontalIcon, + arrowsLeftRight: phosphor.ArrowsLeftRightIcon, + arrowSquareOut: phosphor.ArrowSquareOutIcon, + arrowUDownLeft: phosphor.ArrowUDownLeftIcon, + arrowUp: phosphor.ArrowUpIcon, + arrowUpRight: phosphor.ArrowUpRightIcon, + arrowUUpLeft: phosphor.ArrowUUpLeftIcon, + asteriskSimple: phosphor.AsteriskSimpleIcon, + bank: phosphor.BankIcon, + bookOpenText: phosphor.BookOpenTextIcon, + bracketsCurly: phosphor.BracketsCurlyIcon, + buildings: phosphor.BuildingsIcon, + calculator: phosphor.CalculatorIcon, + calendarBlank: phosphor.CalendarBlankIcon, + calendarCheck: phosphor.CalendarCheckIcon, + calendarDots: phosphor.CalendarDotsIcon, + camera: phosphor.CameraIcon, + caretDown: phosphor.CaretDownIcon, + caretRight: phosphor.CaretRightIcon, + chat: phosphor.ChatIcon, + chatCircle: phosphor.ChatCircleIcon, + check: phosphor.CheckIcon, + checkSquareOffset: phosphor.CheckSquareOffsetIcon, + clipboardText: phosphor.ClipboardTextIcon, + clockClockwise: phosphor.ClockClockwiseIcon, + cloudArrowUp: phosphor.CloudArrowUpIcon, + copy: phosphor.CopyIcon, + creditCard: phosphor.CreditCardIcon, + currencyEur: phosphor.CurrencyEurIcon, + diamondsFour: phosphor.DiamondsFourIcon, + discord: phosphor.DiscordLogoIcon, + dotsSixVertical: phosphor.DotsSixVerticalIcon, + dotsThree: phosphor.DotsThreeIcon, + dotsThreeVertical: phosphor.DotsThreeVerticalIcon, + download: phosphor.DownloadIcon, + envelopeSimple: phosphor.EnvelopeSimpleIcon, + equals: phosphor.EqualsIcon, + eye: phosphor.EyeIcon, + eyeSlash: phosphor.EyeSlashIcon, + flag: phosphor.FlagIcon, + folderOpen: phosphor.FolderOpenIcon, + funnelSimple: phosphor.FunnelSimpleIcon, + gear: phosphor.GearIcon, + gearFine: phosphor.GearFineIcon, + gift: phosphor.GiftIcon, + gitFork: phosphor.GitForkIcon, + githubLogo: phosphor.GithubLogoIcon, + globe: phosphor.GlobeIcon, + globeSimple: phosphor.GlobeSimpleIcon, + googleLogo: phosphor.GoogleLogoIcon, + hourglass: phosphor.HourglassIcon, + houseSimple: phosphor.HouseSimpleIcon, + info: phosphor.InfoIcon, + key: phosphor.KeyIcon, + lifebuoy: phosphor.LifebuoyIcon, + lightbulbFilament: phosphor.LightbulbFilamentIcon, + lightning: phosphor.LightningIcon, + link: phosphor.LinkIcon, + linkSimple: phosphor.LinkSimpleIcon, + list: phosphor.ListIcon, + lockSimple: phosphor.LockSimpleIcon, + lockSimpleOpen: phosphor.LockSimpleOpenIcon, + magnifyingGlass: phosphor.MagnifyingGlassIcon, + mapPin: phosphor.MapPinIcon, + megaphoneSimple: phosphor.MegaphoneSimpleIcon, + minus: phosphor.MinusIcon, + minusCircle: phosphor.MinusCircleIcon, + package: phosphor.PackageIcon, + pencilSimple: phosphor.PencilSimpleIcon, + percent: phosphor.PercentIcon, + plus: phosphor.PlusIcon, + plusCircle: phosphor.PlusCircleIcon, + printer: phosphor.PrinterIcon, + pulse: phosphor.PulseIcon, + pushPin: phosphor.PushPinIcon, + puzzlePiece: phosphor.PuzzlePieceIcon, + qrCode: phosphor.QrCodeIcon, + question: phosphor.QuestionIcon, + receipt: phosphor.ReceiptIcon, + rocketLaunch: phosphor.RocketLaunchIcon, + seal: phosphor.SealIcon, + sealPercent: phosphor.SealPercentIcon, + shapes: phosphor.ShapesIcon, + shareFat: phosphor.ShareFatIcon, + shield: phosphor.ShieldIcon, + shoppingBag: phosphor.ShoppingBagIcon, + shoppingBagOpen: phosphor.ShoppingBagOpenIcon, + sidebarSimple: phosphor.SidebarSimpleIcon, + signOut: phosphor.SignOutIcon, + slackLogo: phosphor.SlackLogoIcon, + sliders: phosphor.SlidersIcon, + squaresFour: phosphor.SquaresFourIcon, + stack: phosphor.StackIcon, + star: phosphor.StarIcon, + storefront: phosphor.StorefrontIcon, + suitcaseSimple: phosphor.SuitcaseSimpleIcon, + tag: phosphor.TagIcon, + target: phosphor.TargetIcon, + ticket: phosphor.TicketIcon, + trash: phosphor.TrashIcon, + treeStructure: phosphor.TreeStructureIcon, + treeView: phosphor.TreeViewIcon, + trendUp: phosphor.TrendUpIcon, + truck: phosphor.TruckIcon, + tShirt: phosphor.TShirtIcon, + upload: phosphor.UploadIcon, + user: phosphor.UserIcon, + userCircle: phosphor.UserCircleIcon, + userRectangle: phosphor.UserRectangleIcon, + users: phosphor.UsersIcon, + usersThree: phosphor.UsersThreeIcon, + warehouse: phosphor.WarehouseIcon, + warning: phosphor.WarningIcon, + warningCircle: phosphor.WarningCircleIcon, + webhooksLogo: phosphor.WebhooksLogoIcon, + whatsappLogo: phosphor.WhatsappLogoIcon, + wrench: phosphor.WrenchIcon, + x: phosphor.XIcon, + xCircle: phosphor.XCircleIcon, } as const diff --git a/packages/app-elements/src/ui/atoms/Icon/index.ts b/packages/app-elements/src/ui/atoms/Icon/index.ts index b8a49e23b..6c8945b83 100644 --- a/packages/app-elements/src/ui/atoms/Icon/index.ts +++ b/packages/app-elements/src/ui/atoms/Icon/index.ts @@ -1 +1 @@ -export { Icon, type IconProps } from './Icon' +export { Icon, type IconProps } from "./Icon" diff --git a/packages/app-elements/src/ui/atoms/PageHeading/PageHeading.test.tsx b/packages/app-elements/src/ui/atoms/PageHeading/PageHeading.test.tsx index b22e679f5..4a74684c0 100644 --- a/packages/app-elements/src/ui/atoms/PageHeading/PageHeading.test.tsx +++ b/packages/app-elements/src/ui/atoms/PageHeading/PageHeading.test.tsx @@ -1,5 +1,5 @@ -import { render, type RenderResult } from '@testing-library/react' -import { PageHeading, type PageHeadingProps } from './PageHeading' +import { type RenderResult, render } from "@testing-library/react" +import { PageHeading, type PageHeadingProps } from "./PageHeading" interface SetupProps extends PageHeadingProps { id: string @@ -14,80 +14,80 @@ const setup = ({ id, ...rest }: SetupProps): SetupResult => { const element = utils.getByTestId(id) return { element, - ...utils + ...utils, } } -describe('PageHeading', () => { - test('Should be rendered', () => { - const { element } = setup({ id: 'heading', title: 'My Page Heading' }) - expect(element.querySelector('h1')?.innerHTML).toBe('My Page Heading') +describe("PageHeading", () => { + test("Should be rendered", () => { + const { element } = setup({ id: "heading", title: "My Page Heading" }) + expect(element.querySelector("h1")?.innerHTML).toBe("My Page Heading") }) - test('Should also render optional description', () => { + test("Should also render optional description", () => { const { getByText } = setup({ - id: 'heading', - title: 'My Page Heading', - description: 'Lorem ipsum...' + id: "heading", + title: "My Page Heading", + description: "Lorem ipsum...", }) - expect(getByText('Lorem ipsum...')).toBeVisible() + expect(getByText("Lorem ipsum...")).toBeVisible() }) - test('Should also render optional badge', () => { + test("Should also render optional badge", () => { const { getByTestId } = setup({ - id: 'heading-w-badge', - title: 'My Page Heading', - badge: { label: 'TEST DATA', variant: 'success' } + id: "heading-w-badge", + title: "My Page Heading", + badge: { label: "TEST DATA", variant: "success" }, }) - const badgeElement = getByTestId('page-heading-badge') + const badgeElement = getByTestId("page-heading-badge") expect(badgeElement).toBeInTheDocument() expect( - badgeElement.querySelector('.text-green-600.bg-green-50') + badgeElement.querySelector(".text-green-600.bg-green-50"), ).toBeInTheDocument() }) - test('Should also have a button when navigationButton is set', () => { + test("Should also have a button when navigationButton is set", () => { const foo: string[] = [] const { element } = setup({ - id: 'heading', - title: 'My Page Heading', - description: 'Lorem ipsum...', + id: "heading", + title: "My Page Heading", + description: "Lorem ipsum...", navigationButton: { - label: 'Go back', - onClick: () => foo.push('bar') - } + label: "Go back", + onClick: () => foo.push("bar"), + }, }) - expect(element.querySelector('button')).toBeVisible() - element.querySelector('button')?.click() - expect(foo.includes('bar')).toBe(true) + expect(element.querySelector("button")).toBeVisible() + element.querySelector("button")?.click() + expect(foo.includes("bar")).toBe(true) }) }) -describe('PageHeading gap', () => { - test('Should have gap top and bottom', () => { +describe("PageHeading gap", () => { + test("Should have gap top and bottom", () => { const { element } = setup({ - id: 'heading', - title: 'My Page Heading' + id: "heading", + title: "My Page Heading", }) - expect(element).toHaveClass('pt-5 md:pt-10 pb-6 md:pb-14') + expect(element).toHaveClass("pt-5 md:pt-10 pb-6 md:pb-14") }) - test('Should have gap only on top', () => { + test("Should have gap only on top", () => { const { element } = setup({ - id: 'heading', - title: 'My Page Heading', - gap: 'only-top' + id: "heading", + title: "My Page Heading", + gap: "only-top", }) - expect(element).toHaveClass('pt-5 md:pt-10') - expect(element).not.toHaveClass('pb-6 md:pb-14') + expect(element).toHaveClass("pt-5 md:pt-10") + expect(element).not.toHaveClass("pb-6 md:pb-14") }) - test('Should have no vertical gap', () => { + test("Should have no vertical gap", () => { const { element } = setup({ - id: 'heading', - title: 'My Page Heading', - gap: 'none' + id: "heading", + title: "My Page Heading", + gap: "none", }) - expect(element.classList.toString()).toBe('w-full') + expect(element.classList.toString()).toBe("w-full") }) }) diff --git a/packages/app-elements/src/ui/atoms/PageHeading/PageHeading.tsx b/packages/app-elements/src/ui/atoms/PageHeading/PageHeading.tsx index 42dc4c982..c3b9cf6eb 100644 --- a/packages/app-elements/src/ui/atoms/PageHeading/PageHeading.tsx +++ b/packages/app-elements/src/ui/atoms/PageHeading/PageHeading.tsx @@ -1,13 +1,13 @@ -import cn from 'classnames' -import { type ReactNode } from 'react' -import { Badge, type BadgeProps } from '../Badge' -import { Icon } from '../Icon' -import { withSkeletonTemplate } from '../SkeletonTemplate' -import { Text } from '../Text' +import cn from "classnames" +import type { ReactNode } from "react" +import { Badge, type BadgeProps } from "../Badge" +import { Icon } from "../Icon" +import { withSkeletonTemplate } from "../SkeletonTemplate" +import { Text } from "../Text" import { PageHeadingToolbar, - type PageHeadingToolbarProps -} from './PageHeadingToolbar' + type PageHeadingToolbarProps, +} from "./PageHeadingToolbar" export interface PageHeadingProps { /** @@ -21,14 +21,14 @@ export interface PageHeadingProps { /** * If `true` removes element vertical paddings */ - gap?: 'none' | 'only-top' | 'only-bottom' | 'both' + gap?: "none" | "only-top" | "only-bottom" | "both" /** * When set, it will render a badge (default as warning variant) */ badge?: { label: string /** @default warning-solid */ - variant?: BadgeProps['variant'] + variant?: BadgeProps["variant"] } /** * When set, it will render a navigation (eg: go back) button on the left side of the first row @@ -42,7 +42,7 @@ export interface PageHeadingProps { * Button icon * @default arrowLeft */ - icon?: 'x' | 'arrowLeft' + icon?: "x" | "arrowLeft" } /** * When set, it will render a proper toolbar on the right side of the first row @@ -52,7 +52,7 @@ export interface PageHeadingProps { const PageHeading = withSkeletonTemplate( ({ - gap = 'both', + gap = "both", badge, navigationButton, title, @@ -65,39 +65,39 @@ const PageHeading = withSkeletonTemplate( return (
{navigationButton != null && ( -
+
{toolbar != null ? : null}
)} {badge != null && ( -
- +
+ {badge.label}
)} -
-

+
+

{title}

{navigationButton == null && toolbar != null ? ( @@ -105,12 +105,12 @@ const PageHeading = withSkeletonTemplate( ) : null}
{description !== null && ( -
{description}
+
{description}
)}

) - } + }, ) -PageHeading.displayName = 'PageHeading' +PageHeading.displayName = "PageHeading" export { PageHeading } diff --git a/packages/app-elements/src/ui/atoms/PageHeading/PageHeadingToolbar.test.tsx b/packages/app-elements/src/ui/atoms/PageHeading/PageHeadingToolbar.test.tsx index 94e6b83c2..439f1c878 100644 --- a/packages/app-elements/src/ui/atoms/PageHeading/PageHeadingToolbar.test.tsx +++ b/packages/app-elements/src/ui/atoms/PageHeading/PageHeadingToolbar.test.tsx @@ -1,99 +1,99 @@ -import { act, fireEvent, render, waitFor } from '@testing-library/react' +import { act, fireEvent, render, waitFor } from "@testing-library/react" import { PageHeadingToolbar, - type PageHeadingToolbarProps -} from './PageHeadingToolbar' + type PageHeadingToolbarProps, +} from "./PageHeadingToolbar" const buttons = [ { - label: 'Primary', - size: 'small', + label: "Primary", + size: "small", onClick: () => { - console.log('Primary') - } + console.log("Primary") + }, }, { - label: 'Secondary', - icon: 'pulse', - variant: 'secondary', - size: 'small', + label: "Secondary", + icon: "pulse", + variant: "secondary", + size: "small", onClick: () => { - console.log('Secondary') - } - } -] satisfies PageHeadingToolbarProps['buttons'] + console.log("Secondary") + }, + }, +] satisfies PageHeadingToolbarProps["buttons"] const dropdownItems = [ [ { - label: 'Edit', + label: "Edit", onClick: () => { - console.log('Edit') - } + console.log("Edit") + }, }, { - label: 'Set metadata', + label: "Set metadata", onClick: () => { - console.log('Set metadata') - } - } + console.log("Set metadata") + }, + }, ], [ { - label: 'Delete', + label: "Delete", onClick: () => { - console.log('Delete') - } - } - ] -] satisfies PageHeadingToolbarProps['dropdownItems'] + console.log("Delete") + }, + }, + ], +] satisfies PageHeadingToolbarProps["dropdownItems"] -describe('PageHeadingToolbar', () => { - it('Should not be rendered', () => { +describe("PageHeadingToolbar", () => { + it("Should not be rendered", () => { const { queryByTestId } = render() - expect(queryByTestId('toolbar')).not.toBeInTheDocument() + expect(queryByTestId("toolbar")).not.toBeInTheDocument() }) - it('Should render items', async () => { + it("Should render items", async () => { const { queryAllByTestId, queryByTestId, getByText } = render( - + , ) - expect(queryAllByTestId('toolbar-button').length).toEqual(2) - expect(queryAllByTestId('toolbar-dropdown-button').length).toEqual(1) - expect(queryByTestId('toolbar-dropdown-button')).not.toHaveClass( - 'md:hidden' + expect(queryAllByTestId("toolbar-button").length).toEqual(2) + expect(queryAllByTestId("toolbar-dropdown-button").length).toEqual(1) + expect(queryByTestId("toolbar-dropdown-button")).not.toHaveClass( + "md:hidden", ) - const dropDownButton = queryByTestId('toolbar-dropdown-button') + const dropDownButton = queryByTestId("toolbar-dropdown-button") if (dropDownButton != null) { act(() => { fireEvent.click(dropDownButton) }) await waitFor(() => { - expect(getByText('Edit')).toBeInTheDocument() - expect(getByText('Set metadata')).toBeInTheDocument() - expect(getByText('Delete')).toBeInTheDocument() + expect(getByText("Edit")).toBeInTheDocument() + expect(getByText("Set metadata")).toBeInTheDocument() + expect(getByText("Delete")).toBeInTheDocument() }) } }) - it('Should not display the dropdown button when empty', async () => { + it("Should not display the dropdown button when empty", async () => { const { queryAllByTestId } = render( { - console.log('Primary') - } - } + console.log("Primary") + }, + }, ]} dropdownItems={[[]]} - /> + />, ) - expect(queryAllByTestId('toolbar-button').length).toEqual(1) - expect(queryAllByTestId('toolbar-dropdown-button').length).toEqual(0) + expect(queryAllByTestId("toolbar-button").length).toEqual(1) + expect(queryAllByTestId("toolbar-dropdown-button").length).toEqual(0) }) }) diff --git a/packages/app-elements/src/ui/atoms/PageHeading/PageHeadingToolbar.tsx b/packages/app-elements/src/ui/atoms/PageHeading/PageHeadingToolbar.tsx index 84f241984..bbdd83793 100644 --- a/packages/app-elements/src/ui/atoms/PageHeading/PageHeadingToolbar.tsx +++ b/packages/app-elements/src/ui/atoms/PageHeading/PageHeadingToolbar.tsx @@ -1,13 +1,12 @@ +import type { DropdownItemProps } from "#ui/composite/Dropdown/DropdownItem" import { Toolbar, type ToolbarItem, - type ToolbarProps -} from '#ui/composite/Toolbar' -import { withSkeletonTemplate } from '../SkeletonTemplate' + type ToolbarProps, +} from "#ui/composite/Toolbar" +import { withSkeletonTemplate } from "../SkeletonTemplate" -import { type DropdownItemProps } from '#ui/composite/Dropdown/DropdownItem' - -type ToolbarButton = Omit +type ToolbarButton = Omit export interface PageHeadingToolbarProps { /** @@ -27,13 +26,13 @@ export interface PageHeadingToolbarProps { export const PageHeadingToolbar = withSkeletonTemplate( ({ buttons = [], dropdownItems = [] }) => { // Initialize the toolbar items list with the buttons - const toolbarItems: ToolbarProps['items'] = buttons.map((button, idx) => { + const toolbarItems: ToolbarProps["items"] = buttons.map((button, idx) => { const isShown = - (button.variant == null || button.variant === 'primary') && idx === 0 + (button.variant == null || button.variant === "primary") && idx === 0 return { ...button, // On mobile devices only the first primary button is shown outside the dropdown - className: !isShown ? 'hidden md:flex' : '' + className: !isShown ? "hidden md:flex" : "", } }) @@ -41,34 +40,34 @@ export const PageHeadingToolbar = withSkeletonTemplate( const buttonsForDropdown: DropdownItemProps[] = buttons .filter( (button, idx) => - (button.variant != null && button.variant !== 'primary') || idx > 0 + (button.variant != null && button.variant !== "primary") || idx > 0, ) .map((button) => { return { ...button, - label: button.label ?? '', - className: 'md:hidden' + label: button.label ?? "", + className: "md:hidden", } }) const [firstDropdownItemsGroup = [], ...otherDropdownItems] = dropdownItems // Calculate the flat array of all dropdown items made of buttons and dropdown items const combinedDropdownItems = [ - buttonsForDropdown.concat(firstDropdownItemsGroup) + buttonsForDropdown.concat(firstDropdownItemsGroup), ].concat(otherDropdownItems) // Add dropdown to toolbar items if (combinedDropdownItems.flat().length > 0) { toolbarItems.push({ - icon: 'dotsThree', - size: 'small', - variant: 'secondary', - className: dropdownItems.flat().length > 0 ? '' : 'flex md:hidden', - dropdownItems: combinedDropdownItems + icon: "dotsThree", + size: "small", + variant: "secondary", + className: dropdownItems.flat().length > 0 ? "" : "flex md:hidden", + dropdownItems: combinedDropdownItems, }) } if (toolbarItems.length > 0) { return } - } + }, ) diff --git a/packages/app-elements/src/ui/atoms/PageHeading/index.tsx b/packages/app-elements/src/ui/atoms/PageHeading/index.tsx index fe83c3912..489965636 100644 --- a/packages/app-elements/src/ui/atoms/PageHeading/index.tsx +++ b/packages/app-elements/src/ui/atoms/PageHeading/index.tsx @@ -1 +1 @@ -export { PageHeading, type PageHeadingProps } from './PageHeading' +export { PageHeading, type PageHeadingProps } from "./PageHeading" diff --git a/packages/app-elements/src/ui/atoms/Pagination.test.tsx b/packages/app-elements/src/ui/atoms/Pagination.test.tsx index 3d1fbe060..8e2da6496 100644 --- a/packages/app-elements/src/ui/atoms/Pagination.test.tsx +++ b/packages/app-elements/src/ui/atoms/Pagination.test.tsx @@ -1,37 +1,37 @@ -import { render, type RenderResult } from '@testing-library/react' -import { Pagination, type PaginationProps } from './Pagination' +import { type RenderResult, render } from "@testing-library/react" +import { Pagination, type PaginationProps } from "./Pagination" type SetupResult = RenderResult & { element: HTMLElement } const setup = (props: PaginationProps): SetupResult => { - const utils = render() - const element = utils.getByTestId('my-pagination') + const utils = render() + const element = utils.getByTestId("my-pagination") return { element, - ...utils + ...utils, } } -describe('Pagination', () => { - test('Should be rendered', () => { +describe("Pagination", () => { + test("Should be rendered", () => { const { element } = setup({ currentPage: 1, onChangePageRequest: () => undefined, - pageCount: 10 + pageCount: 10, }) expect(element).toBeInTheDocument() }) - test('Should be rendered as disabled', () => { + test("Should be rendered as disabled", () => { const { element } = setup({ currentPage: 1, onChangePageRequest: () => undefined, pageCount: 10, - isDisabled: true + isDisabled: true, }) expect(element).toBeInTheDocument() - expect(element.className).toContain('opacity-') + expect(element.className).toContain("opacity-") }) }) diff --git a/packages/app-elements/src/ui/atoms/Pagination.tsx b/packages/app-elements/src/ui/atoms/Pagination.tsx index dc3bf4060..851cbc803 100644 --- a/packages/app-elements/src/ui/atoms/Pagination.tsx +++ b/packages/app-elements/src/ui/atoms/Pagination.tsx @@ -1,7 +1,7 @@ -import { makeSomeAdjacentPages } from '#utils/pagination' -import { CaretLeft, CaretRight } from '@phosphor-icons/react' -import cn from 'classnames' -import { type JSX } from 'react' +import { CaretLeftIcon, CaretRightIcon } from "@phosphor-icons/react" +import cn from "classnames" +import type { JSX } from "react" +import { makeSomeAdjacentPages } from "#utils/pagination" export interface PaginationProps { /** @@ -39,16 +39,16 @@ function Pagination({ // we want to show always 3 buttons, so on first page we need 2 next pages adjacentPagesCount: currentPage === 1 ? 2 : 1, pageCount, - direction: 'forward', - excludeCurrentPage: true + direction: "forward", + excludeCurrentPage: true, }) const prevPages = makeSomeAdjacentPages({ currentPage, adjacentPagesCount: 1, pageCount, - direction: 'backward', - excludeCurrentPage: true + direction: "backward", + excludeCurrentPage: true, }) // hide pagination if is only 1 page @@ -59,29 +59,29 @@ function Pagination({ return (
{currentPage > 1 ? ( { onChangePageRequest(currentPage - 1) }} > - + ) : null} {prevPages.map((p) => ( { onChangePageRequest(p) }} @@ -90,7 +90,7 @@ function Pagination({ ))} - + {currentPage} @@ -99,7 +99,7 @@ function Pagination({ return ( { onChangePageRequest(currentPage + 1) }} > - + ) : null}
@@ -128,7 +128,7 @@ function Pagination({ } interface PaginationButtonProps - extends Omit, 'className'> { + extends Omit, "className"> { isActive?: boolean } @@ -141,11 +141,11 @@ function PaginationButton({ ) } -RemoveButton.displayName = 'RemoveButton' +RemoveButton.displayName = "RemoveButton" diff --git a/packages/app-elements/src/ui/atoms/ScrollToTop.tsx b/packages/app-elements/src/ui/atoms/ScrollToTop.tsx index 70f36337a..a4a0f256b 100644 --- a/packages/app-elements/src/ui/atoms/ScrollToTop.tsx +++ b/packages/app-elements/src/ui/atoms/ScrollToTop.tsx @@ -1,4 +1,4 @@ -import { useEffect, type FC } from 'react' +import { type FC, useEffect } from "react" /** * This component lets the window scrolls back to top once the window.location.pathname is changed diff --git a/packages/app-elements/src/ui/atoms/Section.test.tsx b/packages/app-elements/src/ui/atoms/Section.test.tsx index 964d02a51..abcae0932 100644 --- a/packages/app-elements/src/ui/atoms/Section.test.tsx +++ b/packages/app-elements/src/ui/atoms/Section.test.tsx @@ -1,90 +1,96 @@ -import { render } from '@testing-library/react' -import { Section } from './Section' +import { render } from "@testing-library/react" +import { Section } from "./Section" -describe('Legend', () => { - it('Should be rendered', () => { +describe("Legend", () => { + it("Should be rendered", () => { const { getByLabelText } = render( -
Click me}> +
Click me} + > My section content! -
+
, ) - expect(getByLabelText('Hello world')).toBeInTheDocument() + expect(getByLabelText("Hello world")).toBeInTheDocument() }) - it('Should render as a
when title is defined', () => { + it("Should render as a
when title is defined", () => { const { container, getByRole } = render( -
Click me}> +
Click me} + > My section content! -
+
, ) const [element] = container.children assertToBeDefined(element) - expect(getByRole('banner')).toBeVisible() - expect(getByRole('banner').children.length).toEqual(2) - expect(getByRole('heading').tagName).toEqual('H2') + expect(getByRole("banner")).toBeVisible() + expect(getByRole("banner").children.length).toEqual(2) + expect(getByRole("heading").tagName).toEqual("H2") expect(container.children.length).toEqual(1) expect(element).toBeVisible() - expect(element.tagName).toEqual('SECTION') - expect(element).toHaveAttribute('aria-label', 'Hello world') + expect(element.tagName).toEqual("SECTION") + expect(element).toHaveAttribute("aria-label", "Hello world") }) - it('Should get the innerText from the `title` prop and set a valid `aria-label`', () => { + it("Should get the innerText from the `title` prop and set a valid `aria-label`", () => { const { getByRole } = render(
- This is the title! + This is the title!
} > My section content! - + , ) - expect(getByRole('region')).toHaveAttribute( - 'aria-label', - 'This is the title!' + expect(getByRole("region")).toHaveAttribute( + "aria-label", + "This is the title!", ) }) - it('Should render as a
when title is NOT defined', () => { + it("Should render as a
when title is NOT defined", () => { const { container, getByRole, queryByRole } = render( -
Click me}> +
Click me}> My section content! -
+
, ) const [element] = container.children assertToBeDefined(element) - expect(getByRole('banner')).toBeVisible() - expect(getByRole('banner').children.length).toEqual(1) - expect(queryByRole('heading')).toBeNull() + expect(getByRole("banner")).toBeVisible() + expect(getByRole("banner").children.length).toEqual(1) + expect(queryByRole("heading")).toBeNull() expect(container.children.length).toEqual(1) expect(element).toBeVisible() - expect(element.tagName).toEqual('DIV') - expect(element).not.toHaveAttribute('aria-label') + expect(element.tagName).toEqual("DIV") + expect(element).not.toHaveAttribute("aria-label") }) - it('Should NOT render the header when `title` and `actionButton` are not defined', () => { + it("Should NOT render the header when `title` and `actionButton` are not defined", () => { const { container, queryByRole } = render( -
My section content!
+
My section content!
, ) const [element] = container.children assertToBeDefined(element) - expect(queryByRole('banner')).toBeNull() - expect(queryByRole('heading')).toBeNull() + expect(queryByRole("banner")).toBeNull() + expect(queryByRole("heading")).toBeNull() expect(container.children.length).toEqual(1) expect(element).toBeVisible() - expect(element.tagName).toEqual('DIV') - expect(element).not.toHaveAttribute('aria-label') + expect(element.tagName).toEqual("DIV") + expect(element).not.toHaveAttribute("aria-label") }) }) diff --git a/packages/app-elements/src/ui/atoms/Section.tsx b/packages/app-elements/src/ui/atoms/Section.tsx index 7d30b03ec..9368a25e2 100644 --- a/packages/app-elements/src/ui/atoms/Section.tsx +++ b/packages/app-elements/src/ui/atoms/Section.tsx @@ -1,7 +1,8 @@ -import { withSkeletonTemplate } from '#ui/atoms/SkeletonTemplate' -import { getInnerText } from '#utils/children' -import cn from 'classnames' -import React, { type ReactNode } from 'react' +import cn from "classnames" +import type React from "react" +import type { ReactNode } from "react" +import { withSkeletonTemplate } from "#ui/atoms/SkeletonTemplate" +import { getInnerText } from "#utils/children" export interface SectionProps { /** The content of the section. */ @@ -12,9 +13,9 @@ export interface SectionProps { */ title?: ReactNode /** Size for the title prop. */ - titleSize?: 'normal' | 'small' + titleSize?: "normal" | "small" /** Specify `none` to remove border. */ - border?: 'none' + border?: "none" /** This will render a button on the right side of the row. */ actionButton?: ReactNode /** CSS classes. */ @@ -28,14 +29,14 @@ export const Section = withSkeletonTemplate( ({ children, title, - titleSize = 'normal', + titleSize = "normal", actionButton, border, isLoading, delayMs, ...rest }) => { - const Tag = title != null ? 'section' : 'div' + const Tag = title != null ? "section" : "div" return ( ( > {(title != null || actionButton != null) && (
{title != null && (

{title}

)} {actionButton != null && ( -