@transferwise/comparison-components 17.1.11 → 17.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/dist/es/ComparisonTableHeroContainer-343793c4.js +2 -0
  3. package/dist/es/{ComparisonTableHeroContainer-1393f690.js.map → ComparisonTableHeroContainer-343793c4.js.map} +1 -1
  4. package/dist/es/DeliveryEstimation-0067ca5b.js +2 -0
  5. package/dist/es/{DeliveryEstimation-c096fc7d.js.map → DeliveryEstimation-0067ca5b.js.map} +1 -1
  6. package/dist/es/{ErrorTemplate-3bdc5c9f.js → ErrorTemplate-6b12db1d.js} +2 -2
  7. package/dist/es/{ErrorTemplate-3bdc5c9f.js.map → ErrorTemplate-6b12db1d.js.map} +1 -1
  8. package/dist/es/ExchangeRateIndicator-0071c267.js +2 -0
  9. package/dist/es/ExchangeRateIndicator-0071c267.js.map +1 -0
  10. package/dist/es/ProviderComparisonTable-18faf2b2.js +2 -0
  11. package/dist/es/{ProviderComparisonTable-ff55114a.js.map → ProviderComparisonTable-18faf2b2.js.map} +1 -1
  12. package/dist/es/ReceiveAmount-ab1ea550.js +2 -0
  13. package/dist/es/{ReceiveAmount-a51879fe.js.map → ReceiveAmount-ab1ea550.js.map} +1 -1
  14. package/dist/es/TransferFeeComparisonContainer-32640e8d.js +2 -0
  15. package/dist/es/TransferFeeComparisonContainer-32640e8d.js.map +1 -0
  16. package/dist/es/TravelMoney-bbe3d67e.js +2 -0
  17. package/dist/es/{TravelMoney-c8049afa.js.map → TravelMoney-bbe3d67e.js.map} +1 -1
  18. package/dist/es/comparison/index.js +1 -1
  19. package/dist/es/comparison/index.js.map +1 -1
  20. package/dist/es/comparison-table/index.js +1 -1
  21. package/dist/es/comparison-table-hero/index.js +1 -1
  22. package/dist/es/index-8362de5a.js +2 -0
  23. package/dist/es/{index-5bd6b028.js.map → index-8362de5a.js.map} +1 -1
  24. package/dist/es/index.js +1 -1
  25. package/dist/es/index.js.map +1 -1
  26. package/dist/es/provider-comparison-table/index.js +1 -1
  27. package/dist/es/provider-comparison-widget/index.js +1 -1
  28. package/dist/es/provider-comparison-widget/index.js.map +1 -1
  29. package/dist/es/provider-constants-6f33da72.js +2 -0
  30. package/dist/es/provider-constants-6f33da72.js.map +1 -0
  31. package/dist/es/transfer-fee-comparison/index.js +1 -1
  32. package/dist/es/transformQuotesResponseV2.js +2 -0
  33. package/dist/es/transformQuotesResponseV2.js.map +1 -0
  34. package/dist/es/travel-money/index.js +1 -1
  35. package/dist/umd/comparison-components.css +1 -1
  36. package/dist/umd/comparison-components.js +6 -6
  37. package/dist/umd/comparison-components.js.map +1 -1
  38. package/dist/umd/transfer-fee-comparison/transfer-fee-comparison.css +1 -1
  39. package/dist/umd/transfer-fee-comparison/transfer-fee-comparison.js +5 -5
  40. package/dist/umd/transfer-fee-comparison/transfer-fee-comparison.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/index.js +4 -0
  43. package/src/provider-filters/largest-provider/config.ts +12 -0
  44. package/src/provider-filters/largest-provider/index.ts +3 -0
  45. package/src/provider-filters/largest-provider/largest-provider-filter.spec.js +62 -0
  46. package/src/provider-filters/largest-provider/largest-provider-filter.ts +29 -0
  47. package/src/resources/clients/quotes/__test__/transformQuotesResponseV2.spec.ts +129 -0
  48. package/src/resources/clients/quotes/transformQuotesResponseV2.ts +97 -0
  49. package/src/transfer-fee-comparison/TransferFeeComparison.module.less +29 -0
  50. package/src/transfer-fee-comparison/TransferFeeComparison.tsx +20 -13
  51. package/src/transfer-fee-comparison/TransferFeeComparisonContainer.tsx +10 -4
  52. package/src/transfer-fee-comparison/assets/translations/messages_id.json +1 -1
  53. package/src/transfer-fee-comparison/domain/LegacyComparisons.ts +24 -0
  54. package/src/transfer-fee-comparison/domain/Quote.ts +1 -0
  55. package/src/transfer-fee-comparison/provider-filter-toggle/ProviderFilterToggle.spec.tsx +4 -6
  56. package/src/transfer-fee-comparison/provider-filter-toggle/ProviderFilterToggle.tsx +40 -31
  57. package/src/transfer-fee-comparison/provider-filter-toggle/messages_id.json +2 -2
  58. package/src/transfer-fee-comparison/provider-filter-toggle/provider-filter-toggle.module.less +38 -24
  59. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/TransferFeeComparisonFooter.stories.tsx +1 -0
  60. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/TransferFeeComparisonFooter.tsx +11 -7
  61. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/index.ts +1 -0
  62. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/messages_bg.json +1 -1
  63. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/messages_cs.json +1 -1
  64. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/messages_id.json +2 -2
  65. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/messages_nl.json +1 -1
  66. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/messages_pl.json +2 -2
  67. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/messages_sv.json +1 -1
  68. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/messages_tr.json +2 -2
  69. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/messages_uk.json +1 -1
  70. package/src/transfer-fee-comparison/transfer-fee-comparison-footer/transfer-fee-comparison-footer.module.less +2 -2
  71. package/src/transfer-fee-comparison/transfer-fee-comparison-table-row/fee-makeup-popover/messages_id.json +8 -8
  72. package/src/transfer-fee-comparison/translations.json +22 -22
  73. package/dist/es/ComparisonTableHeroContainer-1393f690.js +0 -2
  74. package/dist/es/DeliveryEstimation-c096fc7d.js +0 -2
  75. package/dist/es/ExchangeRateIndicator-7cefde4a.js +0 -2
  76. package/dist/es/ExchangeRateIndicator-7cefde4a.js.map +0 -1
  77. package/dist/es/ProviderComparisonTable-ff55114a.js +0 -2
  78. package/dist/es/ReceiveAmount-a51879fe.js +0 -2
  79. package/dist/es/TransferFeeComparisonContainer-462fb7b1.js +0 -2
  80. package/dist/es/TransferFeeComparisonContainer-462fb7b1.js.map +0 -1
  81. package/dist/es/TravelMoney-c8049afa.js +0 -2
  82. package/dist/es/createUrl-73595472.js +0 -2
  83. package/dist/es/createUrl-73595472.js.map +0 -1
  84. package/dist/es/index-5bd6b028.js +0 -2
  85. package/src/transfer-fee-comparison/largest-provider-filter.ts +0 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transferwise/comparison-components",
3
- "version": "17.1.11",
3
+ "version": "17.1.15",
4
4
  "description": "Display a comparison table of providers given a route and amount.",
5
5
  "homepage": "https://wise.com/gb/compare/",
6
6
  "main": "dist/umd/comparison-components.js",
package/src/index.js CHANGED
@@ -7,8 +7,10 @@ import { ComparisonTableContainer } from './comparison-table';
7
7
  import { ComparisonTableHero, ComparisonTableHeroContainer } from './comparison-table-hero';
8
8
  import ComparisonTableVersus from './comparison-table-versus';
9
9
  import { ProviderComparison } from './provider-comparison-widget';
10
+ import { hasEnoughProviders as hasEnoughLargestProviders } from './provider-filters/largest-provider';
10
11
  import { fetchQuotes } from './resources/clients';
11
12
  import transformQuotesResponse from './resources/clients/quotes/transformQuotesResponse';
13
+ import transformQuotesResponseV2 from './resources/clients/quotes/transformQuotesResponseV2';
12
14
  import {
13
15
  isWiseQuote,
14
16
  isPartnerQuote,
@@ -44,6 +46,8 @@ export {
44
46
  isPartnerQuote,
45
47
  isBankQuote,
46
48
  isMoneyTransferProviderQuote,
49
+ hasEnoughLargestProviders,
50
+ transformQuotesResponseV2,
47
51
  };
48
52
 
49
53
  export default {
@@ -0,0 +1,12 @@
1
+ import TW_ALIAS from '../../resources/provider-constants';
2
+ const PAYPAL = 'paypal';
3
+ const WESTERN_UNION = 'western-union';
4
+
5
+ export const largestProviderByCountry: Map<string, Array<string>> = new Map([
6
+ ['gb', [PAYPAL, WESTERN_UNION, 'hsbc', 'barclays', 'lloyds', 'moneygram', TW_ALIAS]],
7
+ ['us', [PAYPAL, WESTERN_UNION, 'chase', 'moneygram', 'wells-fargo', 'xoom', TW_ALIAS]],
8
+ ['au', [PAYPAL, WESTERN_UNION, 'commonwealth-bank-of-australia', 'anz', 'westpac', 'national-australia-bank', TW_ALIAS]], //eslint-disable-line prettier/prettier
9
+ ['hk', [PAYPAL, WESTERN_UNION, 'hang-seng', 'hsbc-hk', 'ocbc', 'ccb-hk', TW_ALIAS]],
10
+ ['nl', [PAYPAL, WESTERN_UNION, 'lacaixa', 'bbva', 'sabadell', TW_ALIAS]],
11
+ ['es', [PAYPAL, WESTERN_UNION, 'ing-nl', 'abn-amro-bank', 'rabobank', TW_ALIAS]],
12
+ ]);
@@ -0,0 +1,3 @@
1
+ import { hasEnoughProviders, filter } from './largest-provider-filter';
2
+
3
+ export { hasEnoughProviders, filter };
@@ -0,0 +1,62 @@
1
+ import TW_ALIAS from '../../resources/provider-constants';
2
+
3
+ import { largestProviderByCountry } from './config';
4
+ import { filter, hasEnoughProviders, supportsCountry } from './largest-provider-filter';
5
+
6
+ describe('largest-provider-filter', () => {
7
+ const quotes = [
8
+ { provider: { alias: 'lloyds' } },
9
+ { provider: { alias: TW_ALIAS } },
10
+ { provider: { alias: 'monzo' } },
11
+ { provider: { alias: 'barclays' } },
12
+ { provider: { alias: 'monese' } },
13
+ ];
14
+
15
+ // eslint-disable-next-line unicorn/prefer-spread
16
+ it.each(Array.from(largestProviderByCountry.keys()))(
17
+ 'should return true for the country %s',
18
+ (country) => {
19
+ expect(supportsCountry(country)).toStrictEqual(true);
20
+ },
21
+ );
22
+
23
+ it.each(['invalid', 'in', 'sl'])('should return false for the country %s', (country) => {
24
+ expect(supportsCountry(country)).toStrictEqual(false);
25
+ });
26
+
27
+ it('should filter the quotes based on the largest providers', () => {
28
+ const filteredQuotes = filter(quotes, 'gb');
29
+
30
+ expect(filteredQuotes).toHaveLength(3);
31
+ expect(filteredQuotes).toStrictEqual([
32
+ { provider: { alias: 'lloyds' } },
33
+ { provider: { alias: TW_ALIAS } },
34
+ { provider: { alias: 'barclays' } },
35
+ ]);
36
+ });
37
+
38
+ it('should return the passed in quotes if could not find the supported countries', () => {
39
+ const filteredQuotes = filter(quotes, 'non-supported-country');
40
+
41
+ expect(filteredQuotes).toHaveLength(5);
42
+ expect(filteredQuotes).toStrictEqual(quotes);
43
+ });
44
+
45
+ it('hasEnoughProviders should return true if the country is supported and has the required number of providers', () => {
46
+ const result = hasEnoughProviders(quotes, 'gb', 3);
47
+
48
+ expect(result).toStrictEqual(true);
49
+ });
50
+
51
+ it('hasEnoughProviders should return false if the country is no supported', () => {
52
+ const result = hasEnoughProviders(quotes, 'in', 3);
53
+
54
+ expect(result).toStrictEqual(false);
55
+ });
56
+
57
+ it('hasEnoughProviders should return false if available largest providers are less than the required', () => {
58
+ const result = hasEnoughProviders(quotes, 'in', 1);
59
+
60
+ expect(result).toStrictEqual(false);
61
+ });
62
+ });
@@ -0,0 +1,29 @@
1
+ import Quote from '../../transfer-fee-comparison/domain/Quote';
2
+
3
+ import { largestProviderByCountry } from './config';
4
+
5
+ export const supportsCountry = (country: string): boolean => {
6
+ return largestProviderByCountry.has(country);
7
+ };
8
+
9
+ export const hasEnoughProviders = (
10
+ quotes: Quote[],
11
+ country: string,
12
+ requiredNumberOfProviders = 5,
13
+ ): boolean => {
14
+ if (!supportsCountry(country)) {
15
+ return false;
16
+ }
17
+
18
+ const filteredQuotes = filter(quotes, country);
19
+ return filteredQuotes.length >= requiredNumberOfProviders;
20
+ };
21
+
22
+ export const filter = (quotes: Quote[], country: string): Quote[] => {
23
+ if (!supportsCountry(country)) {
24
+ return quotes;
25
+ }
26
+
27
+ const largestProviders: Array<string> = largestProviderByCountry.get(country) || [];
28
+ return quotes.filter((quote) => largestProviders.includes(quote.provider.alias));
29
+ };
@@ -0,0 +1,129 @@
1
+ import { FeeType } from '../../../../transfer-fee-comparison/domain/Quote';
2
+ import transformQuotesResponseV2 from '../transformQuotesResponseV2';
3
+
4
+ describe('quotes v3 response transformations to the canonical model V2', () => {
5
+ const inputMidmarketRate = 1;
6
+
7
+ it('given two fees, should get the highest one (no hidden fees)', () => {
8
+ const providerWithMoreThanOneFee = {
9
+ providers: [
10
+ {
11
+ name: 'Monzo',
12
+ alias: 'monzo',
13
+ logo: 'https://dq8dwmysp7hk1.cloudfront.net/logos/monzo.svg',
14
+ partner: true,
15
+ quotes: [
16
+ {
17
+ rate: 1,
18
+ fee: 2,
19
+ dateCollected: new Date('2018-10-23T12:03:27'),
20
+ receivedAmount: 1278.44,
21
+ sendAmount: 1280.44,
22
+ },
23
+ {
24
+ rate: 1,
25
+ fee: 3,
26
+ dateCollected: new Date('2018-10-23T12:03:27'),
27
+ receivedAmount: 1277.44,
28
+ sendAmount: 1280.44,
29
+ },
30
+ ],
31
+ },
32
+ ],
33
+ sourceCurrency: 'GBP',
34
+ targetCurrency: 'EUR',
35
+ };
36
+
37
+ const expected = {
38
+ quotes: [
39
+ {
40
+ provider: {
41
+ name: 'Monzo',
42
+ alias: 'monzo',
43
+ logoUrl: 'https://dq8dwmysp7hk1.cloudfront.net/logos/monzo.svg',
44
+ partner: true,
45
+ },
46
+ route: {
47
+ sourceCurrency: 'GBP',
48
+ targetCurrency: 'EUR',
49
+ },
50
+ rate: 1,
51
+ fees: {
52
+ breakdown: [
53
+ {
54
+ type: FeeType.MARKETED,
55
+ amount: { value: 2, currency: 'GBP' },
56
+ },
57
+ ],
58
+ total: { value: 2, currency: 'GBP' },
59
+ },
60
+ receiveAmount: { value: 1278.44, currency: 'EUR' },
61
+ },
62
+ ],
63
+ };
64
+
65
+ expect(transformQuotesResponseV2(providerWithMoreThanOneFee, inputMidmarketRate)).toStrictEqual(
66
+ expected,
67
+ );
68
+ });
69
+
70
+ it('should show hidden fees when the rate is lower than the midmarket rate', () => {
71
+ const providerWithHiddenFee = {
72
+ providers: [
73
+ {
74
+ name: 'Western Union',
75
+ alias: 'western-union',
76
+ logo: 'https://dq8dwmysp7hk1.cloudfront.net/logos/western-union.svg',
77
+ partner: true,
78
+ quotes: [
79
+ {
80
+ rate: 0.98,
81
+ fee: 2,
82
+ dateCollected: new Date('2018-10-23T12:03:27'),
83
+ receivedAmount: 1252.8712,
84
+ sendAmount: 1280.44,
85
+ },
86
+ ],
87
+ },
88
+ ],
89
+ sourceCurrency: 'GBP',
90
+ targetCurrency: 'EUR',
91
+ };
92
+
93
+ const expected = {
94
+ quotes: [
95
+ {
96
+ provider: {
97
+ name: 'Western Union',
98
+ alias: 'western-union',
99
+ logoUrl: 'https://dq8dwmysp7hk1.cloudfront.net/logos/western-union.svg',
100
+ partner: true,
101
+ },
102
+ route: {
103
+ sourceCurrency: 'GBP',
104
+ targetCurrency: 'EUR',
105
+ },
106
+ rate: 0.98,
107
+ fees: {
108
+ breakdown: [
109
+ {
110
+ type: FeeType.MARKETED,
111
+ amount: { value: 2, currency: 'GBP' },
112
+ },
113
+ {
114
+ type: FeeType.HIDDEN,
115
+ amount: { value: 25.569, currency: 'GBP' },
116
+ },
117
+ ],
118
+ total: { value: 27.569, currency: 'GBP' },
119
+ },
120
+ receiveAmount: { value: 1252.8712, currency: 'EUR' },
121
+ },
122
+ ],
123
+ };
124
+
125
+ expect(transformQuotesResponseV2(providerWithHiddenFee, inputMidmarketRate)).toStrictEqual(
126
+ expected,
127
+ );
128
+ });
129
+ });
@@ -0,0 +1,97 @@
1
+ import LegacyComparisons, {
2
+ LegacyQuote,
3
+ } from '../../../transfer-fee-comparison/domain/LegacyComparisons';
4
+ import TransferFeeComparisonQuote, {
5
+ FeeBreakdown,
6
+ FeeType,
7
+ } from '../../../transfer-fee-comparison/domain/Quote';
8
+
9
+ import extractQuote from './extractQuote';
10
+
11
+ export type TransformQuotesResponseType = {
12
+ quotes: TransferFeeComparisonQuote[];
13
+ };
14
+
15
+ function extractHiddenFeeInSourceCurrency(
16
+ sendAmount: number,
17
+ receivedAmount: number,
18
+ marketedFee: number,
19
+ midmarketRate: number,
20
+ ) {
21
+ const expectedReceivedAmount = midmarketRate * (sendAmount - marketedFee);
22
+ const hiddenFeePaidOnTargetCurrency =
23
+ receivedAmount < expectedReceivedAmount ? expectedReceivedAmount - receivedAmount : 0;
24
+
25
+ return parseFloat((hiddenFeePaidOnTargetCurrency * (1 / midmarketRate)).toFixed(3));
26
+ }
27
+
28
+ function extractFees(
29
+ marketedFee: number,
30
+ hiddenFee: number,
31
+ sourceCurrency: string,
32
+ ): FeeBreakdown[] {
33
+ const fees: FeeBreakdown[] = [];
34
+
35
+ if (marketedFee > 0) {
36
+ fees.push({
37
+ type: FeeType.MARKETED,
38
+ amount: { value: marketedFee, currency: sourceCurrency },
39
+ });
40
+ }
41
+
42
+ if (hiddenFee > 0) {
43
+ fees.push({
44
+ type: FeeType.HIDDEN,
45
+ amount: { value: hiddenFee, currency: sourceCurrency },
46
+ });
47
+ }
48
+
49
+ return fees;
50
+ }
51
+
52
+ function transformQuotesResponseV2(
53
+ quotesResults: LegacyComparisons,
54
+ midmarketRate: number,
55
+ ): TransformQuotesResponseType {
56
+ const quotes = quotesResults.providers.map((p) => {
57
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
58
+ const quote: LegacyQuote = extractQuote(p.quotes, quotesResults.targetCurrency);
59
+ const hiddenFee: number = extractHiddenFeeInSourceCurrency(
60
+ quote.sendAmount,
61
+ quote.receivedAmount,
62
+ quote.fee,
63
+ midmarketRate,
64
+ );
65
+
66
+ const feesBreakdown: FeeBreakdown[] = extractFees(
67
+ quote.fee,
68
+ hiddenFee,
69
+ quotesResults.sourceCurrency,
70
+ );
71
+
72
+ return {
73
+ provider: {
74
+ alias: p.alias,
75
+ name: p.name,
76
+ logoUrl: p.logo,
77
+ partner: p.partner,
78
+ },
79
+ route: {
80
+ sourceCurrency: quotesResults.sourceCurrency,
81
+ targetCurrency: quotesResults.targetCurrency,
82
+ },
83
+ rate: quote.rate,
84
+ fees: {
85
+ breakdown: feesBreakdown,
86
+ total: { value: quote.fee + hiddenFee, currency: quotesResults.sourceCurrency },
87
+ },
88
+ receiveAmount: { value: quote.receivedAmount, currency: quotesResults.targetCurrency },
89
+ };
90
+ });
91
+
92
+ return {
93
+ quotes,
94
+ };
95
+ }
96
+
97
+ export default transformQuotesResponseV2;
@@ -0,0 +1,29 @@
1
+ @component-spacing: 24px;
2
+
3
+ .transfer-fee-comparison__container {
4
+ display: flex;
5
+ flex-direction: column;
6
+ justify-content: center;
7
+ }
8
+
9
+ .horizontally-centered() {
10
+ display: flex;
11
+ flex-direction: row;
12
+ justify-content: center;
13
+ }
14
+
15
+ .transfer-fee-comparison__provider-filter-toggle {
16
+ .horizontally-centered();
17
+
18
+ margin-bottom: @component-spacing;
19
+ }
20
+
21
+ .transfer-fee-comparison__table {
22
+ .horizontally-centered();
23
+
24
+ margin-bottom: @component-spacing;
25
+ }
26
+
27
+ .transfer-fee-comparison__footer {
28
+ .horizontally-centered();
29
+ }
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
 
3
+ import styles from './TransferFeeComparison.module.less';
3
4
  import ProviderFilteringOption from './domain/ProviderFilteringOption';
4
5
  import Quote from './domain/Quote';
5
6
  import ProviderFilterToggle from './provider-filter-toggle';
6
- import TransferFeeComparisonFooter from './transfer-fee-comparison-footer/TransferFeeComparisonFooter';
7
+ import TransferFeeComparisonFooter from './transfer-fee-comparison-footer';
7
8
  import TransferFeeComparisonTable from './transfer-fee-comparison-table';
8
9
 
9
10
  export type TransferFeeComparisonProps = {
@@ -26,21 +27,27 @@ const TransferFeeComparison = ({
26
27
  urlLocale,
27
28
  }: TransferFeeComparisonProps): JSX.Element => {
28
29
  return (
29
- <div>
30
+ <div className={styles.transferFeeComparison__container}>
30
31
  {providerFiltering.showToggle ? (
31
- <ProviderFilterToggle
32
- activeToggle={providerFiltering.selectedOption}
33
- onToggle={providerFiltering.onProviderFilteringOptionChange}
32
+ <div className={styles.transferFeeComparison__providerFilterToggle}>
33
+ <ProviderFilterToggle
34
+ activeToggle={providerFiltering.selectedOption}
35
+ onToggle={providerFiltering.onProviderFilteringOptionChange}
36
+ locale={locale}
37
+ />
38
+ </div>
39
+ ) : null}
40
+ <div className={styles.transferFeeComparison__table}>
41
+ <TransferFeeComparisonTable
42
+ quotes={quotes}
43
+ midmarketRate={midmarketRate}
44
+ urlLocale={urlLocale}
34
45
  locale={locale}
35
46
  />
36
- ) : null}
37
- <TransferFeeComparisonTable
38
- quotes={quotes}
39
- midmarketRate={midmarketRate}
40
- urlLocale={urlLocale}
41
- locale={locale}
42
- />
43
- <TransferFeeComparisonFooter urlLocale={urlLocale} />
47
+ </div>
48
+ <div className={styles.transferFeeComparison__footer}>
49
+ <TransferFeeComparisonFooter locale={locale} urlLocale={urlLocale} />
50
+ </div>
44
51
  </div>
45
52
  );
46
53
  };
@@ -1,9 +1,10 @@
1
1
  import React, { useState } from 'react';
2
2
 
3
+ import { filter, hasEnoughProviders } from '../provider-filters/largest-provider';
4
+
3
5
  import TransferFeeComparison from './TransferFeeComparison';
4
6
  import ProviderFilteringOption from './domain/ProviderFilteringOption';
5
7
  import Quote from './domain/Quote';
6
- import { supportsCountry, filter } from './largest-provider-filter';
7
8
 
8
9
  export type TransferFeeComparisonContainerProps = {
9
10
  quotes: Quote[];
@@ -11,6 +12,7 @@ export type TransferFeeComparisonContainerProps = {
11
12
  locale: string;
12
13
  urlLocale: string;
13
14
  enableLargestProviderFiltering: boolean;
15
+ requiredNumberOfLargestProviders?: number;
14
16
  };
15
17
 
16
18
  // TODO: Needs test
@@ -20,6 +22,7 @@ const TransferFeeComparisonContainer = ({
20
22
  locale,
21
23
  urlLocale,
22
24
  enableLargestProviderFiltering,
25
+ requiredNumberOfLargestProviders = 5,
23
26
  }: TransferFeeComparisonContainerProps): JSX.Element => {
24
27
  const [providerFilteringOption, setProviderFilteringOption] = useState(
25
28
  enableLargestProviderFiltering
@@ -31,9 +34,12 @@ const TransferFeeComparisonContainer = ({
31
34
  setProviderFilteringOption(newValue);
32
35
  };
33
36
 
37
+ const filterToggleEnabled =
38
+ enableLargestProviderFiltering &&
39
+ hasEnoughProviders(quotes, urlLocale, requiredNumberOfLargestProviders);
40
+
34
41
  const filteredQuotes: Quote[] =
35
- providerFilteringOption === ProviderFilteringOption.LARGEST_PROVIDERS &&
36
- supportsCountry(urlLocale)
42
+ providerFilteringOption === ProviderFilteringOption.LARGEST_PROVIDERS && filterToggleEnabled
37
43
  ? filter(quotes, urlLocale)
38
44
  : quotes;
39
45
 
@@ -42,7 +48,7 @@ const TransferFeeComparisonContainer = ({
42
48
  quotes={filteredQuotes}
43
49
  midmarketRate={midmarketRate}
44
50
  providerFiltering={{
45
- showToggle: enableLargestProviderFiltering,
51
+ showToggle: filterToggleEnabled,
46
52
  selectedOption: providerFilteringOption,
47
53
  onProviderFilteringOptionChange: onProviderFilteringOptionChange,
48
54
  }}
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "no-hidden-fees": "tanpa biaya tersembunyi",
3
- "hidden-fees": "{{hiddenFee}} hiding in the rate"
3
+ "hidden-fees": "{{hiddenFee}} bersembunyi dalam nilai tukar"
4
4
  }
@@ -0,0 +1,24 @@
1
+ type LegacyComparisons = {
2
+ providers: Provider[];
3
+ sourceCurrency: string;
4
+ targetCurrency: string;
5
+ };
6
+
7
+ type Provider = {
8
+ alias: string;
9
+ name: string;
10
+ logo: string;
11
+ partner: boolean;
12
+ quotes: LegacyQuote[];
13
+ };
14
+
15
+ type LegacyQuote = {
16
+ fee: number;
17
+ rate: number;
18
+ receivedAmount: number;
19
+ sendAmount: number;
20
+ dateCollected: Date;
21
+ };
22
+
23
+ export default LegacyComparisons;
24
+ export type { LegacyQuote };
@@ -30,4 +30,5 @@ type TransferFeeComparisonQuote = {
30
30
  };
31
31
 
32
32
  export default TransferFeeComparisonQuote;
33
+ export type { FeeBreakdown };
33
34
  export { FeeType };
@@ -34,10 +34,8 @@ describe('<ProviderFilterToggle />', () => {
34
34
  render(<ProviderFilterToggle activeToggle={activeToggle} onToggle={onToggle} locale="en" />);
35
35
 
36
36
  // expect:
37
- expect(screen.getByText(activeToggleText)).toHaveClass('providerFilterToggle__activeButton');
38
- expect(screen.getByText(inactiveToggleText)).not.toHaveClass(
39
- 'providerFilterToggle__activeButton',
40
- );
37
+ expect(screen.getByText(activeToggleText)).toBeDisabled();
38
+ expect(screen.getByText(inactiveToggleText)).toBeEnabled();
41
39
  },
42
40
  );
43
41
 
@@ -61,8 +59,8 @@ describe('<ProviderFilterToggle />', () => {
61
59
  );
62
60
 
63
61
  // then::
64
- expect(screen.getByText('Largest providers')).toHaveClass('providerFilterToggle__activeButton');
65
- expect(screen.getByText('All providers')).not.toHaveClass('providerFilterToggle__activeButton');
62
+ expect(screen.getByText('Largest providers')).toBeDisabled();
63
+ expect(screen.getByText('All providers')).toBeEnabled();
66
64
  });
67
65
 
68
66
  it.each(TEST_CASES)(
@@ -3,7 +3,7 @@ import React from 'react';
3
3
  import { FormattedMessage, IntlProvider } from 'react-intl';
4
4
 
5
5
  import ProviderFilteringOption from '../domain/ProviderFilteringOption';
6
- import translationsFile from '../translations.json';
6
+ import translations from '../translations.json';
7
7
 
8
8
  import styles from './provider-filter-toggle.module.less';
9
9
 
@@ -18,43 +18,52 @@ export const ProviderFilterToggle = ({
18
18
  onToggle,
19
19
  locale,
20
20
  }: ProviderFilterToggleProps): JSX.Element => {
21
- const typedTranslationFile: { [language: string]: typeof messages } = translationsFile;
22
- const messages: Record<string, string> = typedTranslationFile[locale];
21
+ const typedTranslations: { [language: string]: typeof messages } = translations;
22
+ const messages: Record<string, string> = typedTranslations[locale];
23
23
 
24
24
  return (
25
25
  <IntlProvider messages={messages} locale={locale} defaultLocale="en">
26
26
  <div className={styles.providerFilterToggle__toggleContainer}>
27
- <Button
28
- className={[
29
- styles.providerFilterToggle__leftButton,
30
- activeToggle === ProviderFilteringOption.LARGEST_PROVIDERS
31
- ? styles.providerFilterToggle__activeButton
32
- : '',
33
- ]}
34
- onClick={() => {
35
- activeToggle === ProviderFilteringOption.ALL_PROVIDERS &&
36
- onToggle(ProviderFilteringOption.LARGEST_PROVIDERS);
37
- }}
38
- >
39
- <FormattedMessage id="largest-providers" />
40
- </Button>
41
- <Button
42
- className={[
43
- styles.providerFilterToggle__rightButton,
44
- activeToggle === ProviderFilteringOption.ALL_PROVIDERS
45
- ? styles.providerFilterToggle__activeButton
46
- : '',
47
- ]}
48
- onClick={() => {
49
- activeToggle === ProviderFilteringOption.LARGEST_PROVIDERS &&
50
- onToggle(ProviderFilteringOption.ALL_PROVIDERS);
51
- }}
52
- >
53
- <FormattedMessage id="all-providers" />
54
- </Button>
27
+ <Toggle
28
+ label={<FormattedMessage id="largest-providers" />}
29
+ value={ProviderFilteringOption.LARGEST_PROVIDERS}
30
+ isActive={activeToggle === ProviderFilteringOption.LARGEST_PROVIDERS}
31
+ onToggle={onToggle}
32
+ />
33
+ <Toggle
34
+ label={<FormattedMessage id="all-providers" />}
35
+ value={ProviderFilteringOption.ALL_PROVIDERS}
36
+ isActive={activeToggle === ProviderFilteringOption.ALL_PROVIDERS}
37
+ onToggle={onToggle}
38
+ />
55
39
  </div>
56
40
  </IntlProvider>
57
41
  );
58
42
  };
59
43
 
44
+ type ToggleProps = {
45
+ label: JSX.Element;
46
+ value: ProviderFilteringOption;
47
+ isActive: boolean;
48
+ onToggle: (newValue: ProviderFilteringOption) => void;
49
+ };
50
+
51
+ const Toggle = ({ label, value, isActive, onToggle }: ToggleProps): JSX.Element => {
52
+ return (
53
+ <Button
54
+ className={`${styles.providerFilterToggle__button} ${
55
+ isActive
56
+ ? styles.providerFilterToggle__buttonActive
57
+ : styles.providerFilterToggle__buttonInactive
58
+ }`}
59
+ onClick={() => {
60
+ !isActive && onToggle(value);
61
+ }}
62
+ disabled={isActive}
63
+ >
64
+ {label}
65
+ </Button>
66
+ );
67
+ };
68
+
60
69
  export default ProviderFilterToggle;
@@ -1,4 +1,4 @@
1
1
  {
2
- "largest-providers": "Largest providers",
3
- "all-providers": "All providers"
2
+ "largest-providers": "Penyedia terbesar",
3
+ "all-providers": "Semua penyedia"
4
4
  }