@tuturuuu/ui 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/package.json +41 -34
  3. package/src/components/ui/currency-input.tsx +65 -23
  4. package/src/components/ui/custom/__tests__/sidebar-context.test.tsx +64 -0
  5. package/src/components/ui/custom/__tests__/sidebar-remote-behavior-bridge.test.tsx +109 -0
  6. package/src/components/ui/custom/combobox.test.tsx +141 -0
  7. package/src/components/ui/custom/combobox.tsx +105 -36
  8. package/src/components/ui/custom/settings/task-settings.tsx +126 -0
  9. package/src/components/ui/custom/settings/task-sound-settings.test.tsx +146 -0
  10. package/src/components/ui/custom/sidebar-context.tsx +68 -6
  11. package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +21 -2
  12. package/src/components/ui/finance/finance-layout.tsx +2 -4
  13. package/src/components/ui/finance/shared/balance-mode-toggle.tsx +35 -0
  14. package/src/components/ui/finance/shared/finance-layout-controls.tsx +43 -0
  15. package/src/components/ui/finance/shared/quick-actions.tsx +14 -6
  16. package/src/components/ui/finance/shared/use-finance-balance-mode.ts +72 -0
  17. package/src/components/ui/finance/shared/wallet-balance-mode.test.ts +66 -0
  18. package/src/components/ui/finance/shared/wallet-balance-mode.ts +42 -0
  19. package/src/components/ui/finance/transactions/form-types.ts +23 -0
  20. package/src/components/ui/finance/transactions/form.tsx +81 -22
  21. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +29 -18
  22. package/src/components/ui/finance/transactions/transaction-card.tsx +75 -43
  23. package/src/components/ui/finance/transactions/transfer-merge.test.ts +90 -0
  24. package/src/components/ui/finance/transactions/transfer-merge.ts +52 -0
  25. package/src/components/ui/finance/transactions/wallet-filter.tsx +21 -2
  26. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-adjustment-dialog.tsx +219 -0
  27. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-amount.tsx +32 -0
  28. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-delete-dialog.tsx +50 -0
  29. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-dialog.tsx +138 -0
  30. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-history-dialog.tsx +617 -0
  31. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-panel.tsx +197 -0
  32. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-sections.tsx +201 -0
  33. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoints.test.tsx +541 -0
  34. package/src/components/ui/finance/wallets/checkpoints/wallet-total-check-dialog.tsx +362 -0
  35. package/src/components/ui/finance/wallets/columns-rendering.test.tsx +125 -0
  36. package/src/components/ui/finance/wallets/columns.test.ts +56 -0
  37. package/src/components/ui/finance/wallets/columns.tsx +196 -43
  38. package/src/components/ui/finance/wallets/form.test.tsx +79 -14
  39. package/src/components/ui/finance/wallets/form.tsx +41 -197
  40. package/src/components/ui/finance/wallets/query-invalidation.ts +3 -0
  41. package/src/components/ui/finance/wallets/wallet-basics-fields.tsx +141 -0
  42. package/src/components/ui/finance/wallets/wallet-credit-fields.tsx +136 -0
  43. package/src/components/ui/finance/wallets/walletId/credit-wallet-summary.tsx +143 -68
  44. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.test.tsx +105 -0
  45. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +120 -16
  46. package/src/components/ui/finance/wallets/walletId/wallet-details-amount.test.tsx +64 -0
  47. package/src/components/ui/finance/wallets/walletId/wallet-details-amount.tsx +226 -6
  48. package/src/components/ui/finance/wallets/walletId/wallet-details-page.test.tsx +71 -5
  49. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +52 -35
  50. package/src/components/ui/finance/wallets/wallets-data-table.test.tsx +171 -0
  51. package/src/components/ui/finance/wallets/wallets-data-table.tsx +132 -29
  52. package/src/components/ui/finance/wallets/wallets-page.test.tsx +117 -36
  53. package/src/components/ui/finance/wallets/wallets-page.tsx +40 -64
  54. package/src/components/ui/storefront/accent-button.tsx +33 -0
  55. package/src/components/ui/storefront/cart-summary.tsx +140 -0
  56. package/src/components/ui/storefront/empty-listings.tsx +32 -0
  57. package/src/components/ui/storefront/hero-panel.tsx +70 -0
  58. package/src/components/ui/storefront/image-panel.tsx +40 -0
  59. package/src/components/ui/storefront/index.ts +12 -0
  60. package/src/components/ui/storefront/listing-card.tsx +129 -0
  61. package/src/components/ui/storefront/storefront-surface.test.tsx +85 -0
  62. package/src/components/ui/storefront/storefront-surface.tsx +235 -0
  63. package/src/components/ui/storefront/types.ts +99 -0
  64. package/src/components/ui/storefront/utils.ts +90 -0
  65. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-move.test.tsx +14 -0
  66. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operations.ts +29 -0
  67. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +134 -0
  68. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +127 -0
  69. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +17 -42
  70. package/src/components/ui/tu-do/boards/boardId/timeline-board-open-task.test.tsx +164 -0
  71. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +25 -16
  72. package/src/components/ui/tu-do/hooks/useTaskDialog.ts +15 -1
  73. package/src/components/ui/tu-do/my-tasks/__tests__/use-task-context-actions.test.ts +11 -0
  74. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +2 -0
  75. package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +124 -7
  76. package/src/components/ui/tu-do/providers/__tests__/task-dialog-provider.test.tsx +217 -5
  77. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +180 -35
  78. package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +222 -26
  79. package/src/components/ui/tu-do/shared/board-client.tsx +1 -3
  80. package/src/components/ui/tu-do/shared/list-view-context-menu.test.tsx +55 -2
  81. package/src/components/ui/tu-do/shared/list-view.tsx +23 -16
  82. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +93 -76
  83. package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +11 -0
  84. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +268 -0
  85. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +243 -0
  86. package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +26 -0
  87. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.test.tsx +129 -0
  88. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.tsx +358 -0
  89. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +1 -1
  90. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +6 -2
  91. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +36 -20
  92. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.test.tsx +41 -1
  93. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.tsx +157 -102
  94. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-form-reset.ts +18 -2
  95. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-realtime-sync.ts +1 -2
  96. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.test.ts +84 -1
  97. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.ts +5 -1
  98. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +5 -3
  99. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +300 -172
  100. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +959 -340
  101. package/src/components/ui/tu-do/shared/task-sound-effects.test.ts +189 -0
  102. package/src/components/ui/tu-do/shared/task-sound-effects.tsx +468 -0
  103. package/src/hooks/__tests__/use-task-actions.test.tsx +61 -0
  104. package/src/hooks/use-task-actions.ts +45 -0
  105. package/src/hooks/useBoardRealtime.ts +54 -1
  106. package/src/hooks/useBoardRealtimeEventHandler.ts +169 -4
  107. package/src/hooks/useTaskUserRealtime.ts +338 -0
@@ -2,10 +2,13 @@
2
2
 
3
3
  import type { ColumnDef } from '@tanstack/react-table';
4
4
  import {
5
+ BookOpen,
5
6
  Check,
6
7
  CreditCard,
8
+ Scale,
7
9
  TrendingDown,
8
10
  TrendingUp,
11
+ TriangleAlert,
9
12
  Wallet as WalletIcon,
10
13
  X,
11
14
  } from '@tuturuuu/icons';
@@ -19,13 +22,22 @@ import { convertCurrency } from '@tuturuuu/utils/exchange-rates';
19
22
  import { cn, formatCurrency } from '@tuturuuu/utils/format';
20
23
  import moment from 'moment';
21
24
  import Link from 'next/link';
25
+ import { useTranslations } from 'next-intl';
26
+ import type { FocusEvent, ReactNode } from 'react';
27
+ import { useState } from 'react';
28
+ import type { FinanceBalanceMode } from '../shared/use-finance-balance-mode';
22
29
  import {
23
30
  FINANCE_HIDDEN_AMOUNT,
24
31
  useFinanceConfidentialVisibility,
25
32
  } from '../shared/use-finance-confidential-visibility';
33
+ import {
34
+ getWalletBalanceTone,
35
+ resolveWalletBalanceForMode,
36
+ } from '../shared/wallet-balance-mode';
26
37
  import { WalletIconDisplay } from './wallet-icon-display';
27
38
 
28
39
  interface WalletExtraData {
40
+ balanceMode?: FinanceBalanceMode;
29
41
  canUpdateWallets?: boolean;
30
42
  canDeleteWallets?: boolean;
31
43
  currency?: string;
@@ -33,33 +45,138 @@ interface WalletExtraData {
33
45
  isPersonalWorkspace?: boolean;
34
46
  }
35
47
 
48
+ type AmountBadgeTone = ReturnType<typeof getWalletBalanceTone> | 'varied';
49
+
50
+ function getAmountBadgeClassName(tone: AmountBadgeTone) {
51
+ if (tone === 'varied') {
52
+ return 'border-dynamic-orange/40 bg-dynamic-orange/10 font-semibold text-dynamic-orange';
53
+ }
54
+
55
+ if (tone === 'positive') {
56
+ return 'border-dynamic-green/30 bg-dynamic-green/10 font-semibold text-dynamic-green';
57
+ }
58
+
59
+ if (tone === 'negative') {
60
+ return 'border-dynamic-red/30 bg-dynamic-red/10 font-semibold text-dynamic-red';
61
+ }
62
+
63
+ return 'font-semibold text-muted-foreground';
64
+ }
65
+
66
+ function getContextAmountBadgeClassName(tone: 'ledger' | 'variance') {
67
+ if (tone === 'ledger') {
68
+ return 'border-dynamic-blue/30 bg-dynamic-blue/10 font-semibold text-dynamic-blue';
69
+ }
70
+
71
+ return 'border-dynamic-purple/30 bg-dynamic-purple/10 font-semibold text-dynamic-purple';
72
+ }
73
+
74
+ function AmountBadge({
75
+ children,
76
+ icon,
77
+ label,
78
+ tone,
79
+ }: {
80
+ children: ReactNode;
81
+ icon?: ReactNode;
82
+ label?: string;
83
+ tone: AmountBadgeTone;
84
+ }) {
85
+ return (
86
+ <Badge
87
+ variant="outline"
88
+ data-wallet-balance-badge={tone}
89
+ className={cn(
90
+ 'flex w-fit items-center gap-1 whitespace-nowrap',
91
+ getAmountBadgeClassName(tone)
92
+ )}
93
+ >
94
+ {icon}
95
+ {label && <span className="font-medium opacity-75">{label}</span>}
96
+ <span>{children}</span>
97
+ </Badge>
98
+ );
99
+ }
100
+
101
+ function ContextAmountBadge({
102
+ children,
103
+ icon,
104
+ label,
105
+ tone,
106
+ }: {
107
+ children: ReactNode;
108
+ icon?: ReactNode;
109
+ label: string;
110
+ tone: 'ledger' | 'variance';
111
+ }) {
112
+ return (
113
+ <Badge
114
+ variant="outline"
115
+ data-wallet-balance-context-badge={tone}
116
+ className={cn(
117
+ 'flex w-fit items-center gap-1 whitespace-nowrap',
118
+ getContextAmountBadgeClassName(tone)
119
+ )}
120
+ >
121
+ {icon}
122
+ <span className="font-medium opacity-75">{label}</span>
123
+ <span>{children}</span>
124
+ </Badge>
125
+ );
126
+ }
127
+
36
128
  function WalletBalanceCell({
37
- balance,
129
+ balanceMode,
130
+ wallet,
38
131
  walletCurrency,
39
132
  workspaceCurrency,
40
133
  exchangeRates,
41
134
  }: {
42
- balance: number;
135
+ balanceMode: FinanceBalanceMode;
136
+ wallet: Wallet;
43
137
  walletCurrency: string;
44
138
  workspaceCurrency: string;
45
139
  exchangeRates?: ExchangeRate[];
46
140
  }) {
141
+ const t = useTranslations('wallet-checkpoints');
142
+ const [isAuditContextOpen, setIsAuditContextOpen] = useState(false);
47
143
  const { isConfidential: areNumbersHidden } =
48
144
  useFinanceConfidentialVisibility();
145
+ const {
146
+ auditStatus,
147
+ auditVariance,
148
+ contextBalance,
149
+ displayBalance,
150
+ hasAuditedBalance,
151
+ isAuditedMode,
152
+ } = resolveWalletBalanceForMode(wallet, balanceMode);
153
+ const contextLabel = isAuditedMode ? t('ledger') : t('audited');
154
+ const showAuditContext =
155
+ hasAuditedBalance &&
156
+ auditStatus &&
157
+ auditStatus !== 'clean' &&
158
+ auditStatus !== 'no_checkpoint' &&
159
+ auditVariance !== null &&
160
+ auditVariance !== 0;
49
161
 
50
- const formattedBalance = formatCurrency(balance, walletCurrency, undefined, {
51
- signDisplay: 'auto',
52
- });
162
+ const formattedBalance = formatCurrency(
163
+ displayBalance,
164
+ walletCurrency,
165
+ undefined,
166
+ {
167
+ signDisplay: 'auto',
168
+ }
169
+ );
53
170
 
54
171
  let convertedText: string | null = null;
55
172
  if (
56
173
  walletCurrency !== workspaceCurrency &&
57
174
  exchangeRates &&
58
175
  exchangeRates.length > 0 &&
59
- balance !== 0
176
+ displayBalance !== 0
60
177
  ) {
61
178
  const converted = convertCurrency(
62
- balance,
179
+ displayBalance,
63
180
  walletCurrency,
64
181
  workspaceCurrency,
65
182
  exchangeRates
@@ -82,51 +199,84 @@ function WalletBalanceCell({
82
199
  );
83
200
  }
84
201
 
85
- const isPositive = balance > 0;
86
- const isNegative = balance < 0;
87
- const isNeutral = balance === 0;
202
+ const balanceTone = showAuditContext
203
+ ? 'varied'
204
+ : getWalletBalanceTone(displayBalance);
205
+
206
+ function handleAuditContextBlur(event: FocusEvent<HTMLDivElement>) {
207
+ const nextTarget = event.relatedTarget as Node | null;
208
+ if (!nextTarget || !event.currentTarget.contains(nextTarget)) {
209
+ setIsAuditContextOpen(false);
210
+ }
211
+ }
88
212
 
89
213
  return (
90
214
  <div className="flex flex-col gap-0.5">
91
- <div className="flex items-center gap-2">
92
- {isPositive && (
93
- <Badge
94
- variant="outline"
95
- className={cn(
96
- 'border-dynamic-green/30 bg-dynamic-green/10 font-semibold text-dynamic-green',
97
- 'flex items-center gap-1'
98
- )}
215
+ <div className="flex flex-wrap items-center gap-1.5">
216
+ <div
217
+ data-wallet-balance-trigger
218
+ className="relative w-fit rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
219
+ onBlur={handleAuditContextBlur}
220
+ onFocus={() => setIsAuditContextOpen(true)}
221
+ onMouseEnter={() => setIsAuditContextOpen(true)}
222
+ onMouseLeave={() => setIsAuditContextOpen(false)}
223
+ tabIndex={showAuditContext ? 0 : undefined}
224
+ >
225
+ <AmountBadge
226
+ tone={balanceTone}
227
+ icon={
228
+ balanceTone === 'positive' ? (
229
+ <TrendingUp className="h-3 w-3" />
230
+ ) : balanceTone === 'negative' ? (
231
+ <TrendingDown className="h-3 w-3" />
232
+ ) : balanceTone === 'varied' ? (
233
+ <TriangleAlert className="h-3 w-3" />
234
+ ) : undefined
235
+ }
99
236
  >
100
- <TrendingUp className="h-3 w-3" />
101
237
  {formattedBalance}
102
- </Badge>
103
- )}
104
- {isNegative && (
105
- <Badge
106
- variant="outline"
107
- className={cn(
108
- 'border-dynamic-red/30 bg-dynamic-red/10 font-semibold text-dynamic-red',
109
- 'flex items-center gap-1'
238
+ </AmountBadge>
239
+ {showAuditContext &&
240
+ isAuditContextOpen &&
241
+ typeof contextBalance === 'number' && (
242
+ <div className="absolute top-full left-0 z-20 mt-1 flex min-w-max flex-wrap items-center gap-1.5 rounded-md border bg-popover p-1.5 shadow-md">
243
+ <ContextAmountBadge
244
+ tone="ledger"
245
+ icon={<BookOpen className="h-3 w-3" />}
246
+ label={contextLabel}
247
+ >
248
+ {formatCurrency(contextBalance, walletCurrency, undefined, {
249
+ signDisplay: 'auto',
250
+ })}
251
+ </ContextAmountBadge>
252
+ <ContextAmountBadge
253
+ tone="variance"
254
+ icon={<Scale className="h-3 w-3" />}
255
+ label={t('variance')}
256
+ >
257
+ {formatCurrency(
258
+ auditVariance ?? 0,
259
+ walletCurrency,
260
+ undefined,
261
+ {
262
+ signDisplay: 'always',
263
+ }
264
+ )}
265
+ </ContextAmountBadge>
266
+ </div>
110
267
  )}
111
- >
112
- <TrendingDown className="h-3 w-3" />
113
- {formattedBalance}
114
- </Badge>
115
- )}
116
- {isNeutral && (
117
- <Badge
118
- variant="outline"
119
- className="font-semibold text-muted-foreground"
120
- >
121
- {formattedBalance}
122
- </Badge>
123
- )}
268
+ </div>
124
269
  </div>
125
270
  {convertedText && (
126
271
  <span className="text-muted-foreground text-xs">
127
272
  {'\u2248'} {convertedText}
128
273
  </span>
129
274
  )}
275
+ {isAuditedMode && auditStatus === 'no_checkpoint' && (
276
+ <span className="text-muted-foreground text-xs">
277
+ {t('no_checkpoint_short')}
278
+ </span>
279
+ )}
130
280
  </div>
131
281
  );
132
282
  }
@@ -139,6 +289,7 @@ export const walletColumns = ({
139
289
  extraData?: WalletExtraData;
140
290
  }): ColumnDef<Wallet>[] => {
141
291
  const workspaceCurrency = extraData?.currency || 'USD';
292
+ const balanceMode = extraData?.balanceMode ?? 'ledger';
142
293
 
143
294
  return [
144
295
  // {
@@ -213,7 +364,9 @@ export const walletColumns = ({
213
364
  cell: ({ row }) => <div>{row.getValue('description') || '-'}</div>,
214
365
  },
215
366
  {
216
- accessorKey: 'balance',
367
+ id: 'balance',
368
+ accessorFn: (wallet) =>
369
+ resolveWalletBalanceForMode(wallet, balanceMode).displayBalance,
217
370
  header: ({ column }) => (
218
371
  <DataTableColumnHeader
219
372
  t={t}
@@ -222,11 +375,11 @@ export const walletColumns = ({
222
375
  />
223
376
  ),
224
377
  cell: ({ row }) => {
225
- const balance = Number(row.getValue('balance')) || 0;
226
378
  const walletCurrency = row.original.currency || workspaceCurrency;
227
379
  return (
228
380
  <WalletBalanceCell
229
- balance={balance}
381
+ balanceMode={balanceMode}
382
+ wallet={row.original}
230
383
  walletCurrency={walletCurrency}
231
384
  workspaceCurrency={workspaceCurrency}
232
385
  exchangeRates={extraData?.exchangeRates}
@@ -1,6 +1,8 @@
1
1
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
- import { render, screen, waitFor } from '@testing-library/react';
2
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3
+ import type { ComponentProps } from 'react';
3
4
  import { beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import type { WalletFormValues } from './form';
4
6
  import { WalletForm } from './form';
5
7
 
6
8
  vi.mock('@tuturuuu/ui/hooks/use-workspace-currency', () => ({
@@ -23,27 +25,47 @@ vi.mock('./wallet-icon-image-picker', () => ({
23
25
  WalletIconImagePicker: () => null,
24
26
  }));
25
27
 
26
- function renderWalletForm() {
28
+ function renderWalletForm(
29
+ options: {
30
+ data?: ComponentProps<typeof WalletForm>['data'];
31
+ defaultType?: WalletFormValues['type'];
32
+ } = {}
33
+ ) {
34
+ const data =
35
+ 'data' in options
36
+ ? options.data
37
+ : ({
38
+ balance: 1234,
39
+ currency: 'USD',
40
+ id: 'wallet-1',
41
+ name: 'Primary',
42
+ type: 'STANDARD',
43
+ } as never);
44
+ const { defaultType } = options;
45
+
27
46
  const queryClient = new QueryClient();
28
47
 
29
48
  return render(
30
49
  <QueryClientProvider client={queryClient}>
31
- <WalletForm
32
- wsId="ws-1"
33
- data={
34
- {
35
- balance: 1234,
36
- currency: 'USD',
37
- id: 'wallet-1',
38
- name: 'Primary',
39
- type: 'STANDARD',
40
- } as never
41
- }
42
- />
50
+ <WalletForm wsId="ws-1" data={data} defaultType={defaultType} />
43
51
  </QueryClientProvider>
44
52
  );
45
53
  }
46
54
 
55
+ function typeIntoCurrencyInput(input: HTMLInputElement, value: string) {
56
+ fireEvent.focus(input);
57
+
58
+ for (const character of value) {
59
+ const nextValue = `${input.value}${character}`;
60
+ fireEvent.change(input, {
61
+ target: {
62
+ selectionStart: nextValue.length,
63
+ value: nextValue,
64
+ },
65
+ });
66
+ }
67
+ }
68
+
47
69
  describe('WalletForm', () => {
48
70
  beforeEach(() => {
49
71
  vi.clearAllMocks();
@@ -75,4 +97,47 @@ describe('WalletForm', () => {
75
97
  ).toHaveValue('1,234')
76
98
  );
77
99
  });
100
+
101
+ it('opens credit-card create flows with credit defaults', () => {
102
+ renderWalletForm({
103
+ data: undefined,
104
+ defaultType: 'CREDIT',
105
+ });
106
+
107
+ expect(
108
+ screen.getByText('wallet-data-table.wallet_type_credit_description')
109
+ ).toBeInTheDocument();
110
+ expect(screen.getByLabelText('wallet-data-table.credit_limit')).toHaveValue(
111
+ ''
112
+ );
113
+ expect(
114
+ screen.getByLabelText('wallet-data-table.statement_date')
115
+ ).toHaveValue(1);
116
+ expect(screen.getByLabelText('wallet-data-table.payment_date')).toHaveValue(
117
+ 15
118
+ );
119
+ });
120
+
121
+ it('keeps accepting large credit limits after locale grouping is inserted', () => {
122
+ renderWalletForm({
123
+ data: {
124
+ balance: 0,
125
+ currency: 'VND',
126
+ id: 'wallet-1',
127
+ limit: undefined,
128
+ name: 'Credit Wallet',
129
+ payment_date: 25,
130
+ statement_date: 10,
131
+ type: 'CREDIT',
132
+ } as never,
133
+ });
134
+
135
+ const creditLimitInput = screen.getByLabelText(
136
+ 'wallet-data-table.credit_limit'
137
+ ) as HTMLInputElement;
138
+
139
+ typeIntoCurrencyInput(creditLimitInput, '39999');
140
+
141
+ expect(creditLimitInput).toHaveValue('39.999');
142
+ });
78
143
  });