@tuturuuu/ui 0.18.0 → 0.19.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 (63) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/package.json +70 -69
  3. package/src/components/ui/calendar-app/calendar-client-page.tsx +19 -1
  4. package/src/components/ui/calendar-app/calendar-page-shell.tsx +37 -16
  5. package/src/components/ui/calendar-app/components/calendar-connections-manager-helpers.ts +70 -0
  6. package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +516 -578
  7. package/src/components/ui/calendar-app/components/calendar-connections-unified.tsx +1 -0
  8. package/src/components/ui/calendar-app/components/calendar-sync-settings-panel.tsx +155 -0
  9. package/src/components/ui/calendar-app/components/merge-provider-calendars.test.ts +46 -0
  10. package/src/components/ui/calendar-app/components/merge-provider-calendars.ts +81 -0
  11. package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +22 -129
  12. package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +17 -3
  13. package/src/components/ui/chat/chat-agent-details-utils.tsx +2 -0
  14. package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +17 -6
  15. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +35 -0
  16. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +17 -0
  17. package/src/components/ui/custom/nav-link.test.tsx +1 -0
  18. package/src/components/ui/custom/nav-link.tsx +4 -1
  19. package/src/components/ui/custom/settings-dialog-shell.tsx +23 -14
  20. package/src/components/ui/custom/statistic-card.tsx +3 -5
  21. package/src/components/ui/custom/structure.test.tsx +4 -0
  22. package/src/components/ui/custom/user-filters.tsx +4 -7
  23. package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +1 -1
  24. package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +1 -1
  25. package/src/components/ui/custom/workspace-select.tsx +38 -53
  26. package/src/components/ui/finance/analytics/category-spending-chart.test.tsx +3 -3
  27. package/src/components/ui/finance/budgets/budget-card.test.tsx +3 -3
  28. package/src/components/ui/finance/debts/debt-loan-card.test.tsx +3 -3
  29. package/src/components/ui/finance/debts/debt-loan-detail-cards.test.tsx +3 -3
  30. package/src/components/ui/finance/invoices/charts/invoice-totals-chart.test.tsx +3 -3
  31. package/src/components/ui/finance/invoices/columns.test.tsx +3 -3
  32. package/src/components/ui/finance/invoices/components/invoice-customer-select-card.tsx +1 -1
  33. package/src/components/ui/finance/shared/numbers-visibility-toggle.test.tsx +1 -1
  34. package/src/components/ui/finance/tags/tag-manager.test.tsx +2 -2
  35. package/src/components/ui/finance/transactions/categories/columns.test.tsx +5 -5
  36. package/src/components/ui/finance/transactions/columns.test.tsx +2 -2
  37. package/src/components/ui/finance/transactions/confidential-field.test.tsx +2 -2
  38. package/src/components/ui/finance/transactions/transaction-edit-dialog.test.tsx +2 -2
  39. package/src/components/ui/finance/transactions/transaction-statistics.test.tsx +3 -3
  40. package/src/components/ui/finance/wallets/form.test.tsx +3 -3
  41. package/src/components/ui/finance/wallets/wallets-data-table.test.tsx +10 -0
  42. package/src/components/ui/legacy/calendar/calendar-cell.tsx +1 -1
  43. package/src/components/ui/storefront/accent-button.tsx +4 -1
  44. package/src/components/ui/storefront/browse-panel.tsx +6 -1
  45. package/src/components/ui/storefront/cart-summary-parts.tsx +18 -4
  46. package/src/components/ui/storefront/cart-summary.tsx +22 -6
  47. package/src/components/ui/storefront/hero-panel.tsx +13 -17
  48. package/src/components/ui/storefront/index.ts +2 -0
  49. package/src/components/ui/storefront/listing-card.tsx +6 -4
  50. package/src/components/ui/storefront/product-detail.tsx +51 -21
  51. package/src/components/ui/storefront/product-dialog.tsx +8 -1
  52. package/src/components/ui/storefront/storefront-surface.test.tsx +79 -3
  53. package/src/components/ui/storefront/storefront-surface.tsx +151 -100
  54. package/src/components/ui/storefront/types.ts +3 -0
  55. package/src/components/ui/storefront/utils.ts +5 -1
  56. package/src/components/ui/text-editor/__tests__/task-mention-checkbox.test.ts +23 -6
  57. package/src/components/ui/text-editor/__tests__/upload-placeholder.test.ts +1 -10
  58. package/src/components/ui/text-editor/color-controls.tsx +1 -1
  59. package/src/globals.css +20 -0
  60. package/src/globals.test.ts +75 -0
  61. package/src/hooks/__tests__/use-calendar-sync.test.tsx +103 -1
  62. package/src/hooks/use-calendar-sync.tsx +51 -14
  63. package/src/lib/task-personal-external.ts +1 -1
@@ -1,6 +1,12 @@
1
1
  'use client';
2
2
 
3
- import { ArrowLeft, ChevronDown, ChevronRight, Search } from '@tuturuuu/icons';
3
+ import {
4
+ ArrowLeft,
5
+ ChevronDown,
6
+ ChevronRight,
7
+ Search,
8
+ X,
9
+ } from '@tuturuuu/icons';
4
10
  import {
5
11
  Breadcrumb,
6
12
  BreadcrumbItem,
@@ -395,18 +401,7 @@ export function SettingsDialogShell({
395
401
  </SidebarContent>
396
402
  </Sidebar>
397
403
  <main className="flex h-full flex-1 flex-col overflow-hidden bg-background">
398
- <header className="flex h-14 shrink-0 items-center gap-2 border-b px-4 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12 md:px-6">
399
- <DialogClose asChild>
400
- <Button
401
- type="button"
402
- variant="ghost"
403
- size="icon"
404
- className="shrink-0 text-muted-foreground md:hidden"
405
- >
406
- <ArrowLeft className="h-4 w-4" />
407
- <span className="sr-only">{t('settings.back_to_app')}</span>
408
- </Button>
409
- </DialogClose>
404
+ <header className="flex h-14 shrink-0 items-center gap-2 border-b px-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12 md:px-6">
410
405
  <div className="flex flex-1 items-center gap-2 md:flex-initial">
411
406
  {isMobile && (
412
407
  <Drawer open={mobileNavOpen} onOpenChange={setMobileNavOpen}>
@@ -510,8 +505,22 @@ export function SettingsDialogShell({
510
505
  </BreadcrumbList>
511
506
  </Breadcrumb>
512
507
  </div>
508
+ <DialogClose asChild>
509
+ <Button
510
+ type="button"
511
+ variant="ghost"
512
+ size="icon"
513
+ className="shrink-0 text-muted-foreground md:hidden"
514
+ >
515
+ <X
516
+ className="h-4 w-4"
517
+ data-testid="settings-mobile-close-icon"
518
+ />
519
+ <span className="sr-only">{t('common.close')}</span>
520
+ </Button>
521
+ </DialogClose>
513
522
  </header>
514
- <div className="flex flex-1 flex-col gap-4 overflow-y-auto p-6">
523
+ <div className="flex flex-1 flex-col gap-4 overflow-y-auto p-2 md:p-6">
515
524
  <div className="mx-auto w-full max-w-4xl space-y-6">
516
525
  {showContentHeader && (
517
526
  <>
@@ -80,9 +80,7 @@ const StatisticCard = ({
80
80
  <Link
81
81
  href={href}
82
82
  onClick={onClick}
83
- className={`group flex flex-col rounded-lg border transition-all duration-300 ${
84
- onClick || href ? 'hover:rounded-xl' : 'cursor-default'
85
- } ${generateOuterColor(!!onClick || !!href)} ${className || ''}`}
83
+ className={`group flex flex-col rounded-lg border transition-all duration-300 hover:rounded-xl ${generateOuterColor(true)} ${className || ''}`}
86
84
  >
87
85
  {cardContent}
88
86
  </Link>
@@ -93,8 +91,8 @@ const StatisticCard = ({
93
91
  type="button"
94
92
  onClick={onClick}
95
93
  className={`group flex flex-col rounded-lg border transition duration-300 ${
96
- onClick || href ? 'hover:rounded-xl' : 'cursor-default'
97
- } ${generateOuterColor(!!onClick || !!href)} ${className || ''}`}
94
+ onClick ? 'hover:rounded-xl' : 'cursor-default'
95
+ } ${generateOuterColor(!!onClick)} ${className || ''}`}
98
96
  >
99
97
  {cardContent}
100
98
  </button>
@@ -25,5 +25,9 @@ describe('Structure', () => {
25
25
  expect(container.querySelector('aside')).not.toBeInTheDocument();
26
26
  expect(container.querySelector('nav')).not.toBeInTheDocument();
27
27
  expect(container.querySelector('main')).toHaveClass('md:pl-0');
28
+ expect(screen.getByText('Page content').parentElement).toHaveClass(
29
+ 'p-2',
30
+ 'md:p-4'
31
+ );
28
32
  });
29
33
  });
@@ -260,7 +260,7 @@ export function Filter({
260
260
  {selectedSize}
261
261
  </Badge>
262
262
  <div className="hidden space-x-1 lg:flex">
263
- {(multiple && selectedSize > 2) || alwaysShowNumber ? (
263
+ {selectedSize > 2 || alwaysShowNumber ? (
264
264
  <Badge
265
265
  variant="secondary"
266
266
  className="rounded-sm bg-background/80 px-1 font-normal text-foreground hover:bg-background/80"
@@ -270,7 +270,7 @@ export function Filter({
270
270
  ) : (
271
271
  options
272
272
  .filter((option) => selectedValues.has(option.value))
273
- .slice(0, multiple ? 2 : 1)
273
+ .slice(0, 2)
274
274
  .map((option) => (
275
275
  <Badge
276
276
  variant="secondary"
@@ -404,7 +404,7 @@ export function Filter({
404
404
  <div className="grid items-center gap-1 md:flex">
405
405
  <CommandItem
406
406
  onSelect={() => {
407
- if (selectedSize === 0 && multiple)
407
+ if (selectedSize === 0)
408
408
  return setSelectedValues(
409
409
  new Set(options.map((option) => option.value))
410
410
  );
@@ -417,15 +417,12 @@ export function Filter({
417
417
  className="w-full justify-center text-center"
418
418
  disabled={
419
419
  applying ||
420
- (!multiple &&
421
- oldValues.size === 0 &&
422
- selectedSize === 0) ||
423
420
  options.every(
424
421
  (option) => option.checked && option.disabled
425
422
  )
426
423
  }
427
424
  >
428
- {selectedSize === 0 && multiple ? (
425
+ {selectedSize === 0 ? (
429
426
  <>
430
427
  <CheckCheck className="mr-2 h-4 w-4" />
431
428
  <span className="line-clamp-1">Select All</span>
@@ -33,7 +33,7 @@ export function WorkspaceAccessPageHeader({
33
33
  {
34
34
  accent: 'text-dynamic-blue',
35
35
  icon: <Users className="h-4 w-4" />,
36
- label: t('common.total'),
36
+ label: t('ws-members.total'),
37
37
  value: totalCount,
38
38
  },
39
39
  {
@@ -94,7 +94,7 @@ export function WorkspaceAccessTabsToolbar({
94
94
  <Input
95
95
  value={search}
96
96
  onChange={(event) => onSearchChange(event.target.value)}
97
- placeholder={t('common.search')}
97
+ placeholder={t('ws-members.search_members')}
98
98
  className="pl-9"
99
99
  />
100
100
  </div>
@@ -9,9 +9,11 @@ import {
9
9
  PlusCircle,
10
10
  Star,
11
11
  } from '@tuturuuu/icons';
12
+ import { InternalApiError } from '@tuturuuu/internal-api/client';
12
13
  import { updateCurrentUserDefaultWorkspace } from '@tuturuuu/internal-api/users';
13
14
  import {
14
15
  acceptWorkspaceInvite,
16
+ createTeamWorkspace,
15
17
  getWorkspace,
16
18
  } from '@tuturuuu/internal-api/workspaces';
17
19
  import type { InternalApiWorkspaceSummary } from '@tuturuuu/types';
@@ -145,6 +147,7 @@ function WorkspaceIcon({
145
147
  export function WorkspaceSelect({
146
148
  wsId,
147
149
  hideLeading,
150
+ standalone,
148
151
  customRedirectSuffix,
149
152
  disableCreateNewWorkspace,
150
153
  fetchWorkspaces,
@@ -156,6 +159,7 @@ export function WorkspaceSelect({
156
159
  }: {
157
160
  wsId: string;
158
161
  hideLeading?: boolean;
162
+ standalone?: boolean;
159
163
  customRedirectSuffix?: string;
160
164
  disableCreateNewWorkspace?: boolean;
161
165
  fetchWorkspaces: () => Promise<InternalApiWorkspaceSummary[]>;
@@ -269,50 +273,31 @@ export function WorkspaceSelect({
269
273
  setLoading(true);
270
274
 
271
275
  try {
272
- const res = await fetch(`/api/v1/workspaces/team`, {
273
- method: 'POST',
274
- headers: {
275
- 'Content-Type': 'application/json',
276
- },
277
- body: JSON.stringify(formData),
278
- });
279
-
280
- if (res.ok) {
281
- form.reset();
282
-
283
- const { id } = await res.json();
284
-
285
- router.push(getWorkspaceLandingPath(id));
286
- router.refresh();
287
-
288
- setShowNewWorkspaceDialog(false);
289
- setLoading(false);
290
- setOpen(false);
291
- } else {
292
- const errorData = await res.json();
293
-
294
- // Check if it's a workspace limit error
295
- if (
296
- res.status === 403 &&
297
- errorData.code === WORKSPACE_LIMIT_ERROR_CODE
298
- ) {
299
- toast.error(t('common.workspace_limit_reached'), {
300
- description: errorData.message,
301
- });
302
- } else {
303
- toast.error(t('common.error_creating_workspace'), {
304
- description:
305
- errorData.message || t('common.workspace_creation_failed'),
306
- });
307
- }
308
-
309
- setLoading(false);
310
- }
276
+ const { id } = await createTeamWorkspace(formData);
277
+ form.reset();
278
+ router.push(getWorkspaceLandingPath(id));
279
+ router.refresh();
280
+ setShowNewWorkspaceDialog(false);
281
+ setOpen(false);
311
282
  } catch (error) {
312
283
  console.error('Error creating workspace:', error);
313
- toast.error(t('common.error_creating_workspace'), {
314
- description: t('common.workspace_creation_failed'),
315
- });
284
+ if (
285
+ error instanceof InternalApiError &&
286
+ error.status === 403 &&
287
+ error.code === WORKSPACE_LIMIT_ERROR_CODE
288
+ ) {
289
+ toast.error(t('common.workspace_limit_reached'), {
290
+ description: error.message,
291
+ });
292
+ } else {
293
+ toast.error(t('common.error_creating_workspace'), {
294
+ description:
295
+ error instanceof Error
296
+ ? error.message
297
+ : t('common.workspace_creation_failed'),
298
+ });
299
+ }
300
+ } finally {
316
301
  setLoading(false);
317
302
  }
318
303
  }
@@ -498,7 +483,7 @@ export function WorkspaceSelect({
498
483
 
499
484
  return (
500
485
  <>
501
- {hideLeading || wsId === ROOT_WORKSPACE_ID || (
486
+ {hideLeading || standalone || wsId === ROOT_WORKSPACE_ID || (
502
487
  <div className="mx-1 h-4 w-px flex-none rotate-30 bg-foreground/20" />
503
488
  )}
504
489
  <Dialog
@@ -787,34 +772,34 @@ export function WorkspaceSelect({
787
772
  <CommandSeparator />
788
773
  <CommandGroup
789
774
  className={cn(
790
- 'grid gap-2',
791
- disableCreateNewWorkspace ? 'grid-cols-1' : 'grid-cols-2'
775
+ '[&_[cmdk-group-items]]:grid [&_[cmdk-group-items]]:gap-1',
776
+ disableCreateNewWorkspace
777
+ ? '[&_[cmdk-group-items]]:grid-cols-1'
778
+ : '[&_[cmdk-group-items]]:grid-cols-2'
792
779
  )}
793
780
  >
794
781
  {!disableCreateNewWorkspace && (
795
782
  <CommandItem
796
- className="justify-center"
783
+ className="h-9 justify-center gap-1.5 px-2"
797
784
  disabled={disableCreateNewWorkspace}
798
785
  onSelect={() => {
799
786
  setOpen(false);
800
787
  setShowNewWorkspaceDialog(true);
801
788
  }}
802
789
  >
803
- <PlusCircle className="h-5 w-5" />
804
- <span className="truncate">
805
- {t('common.create_new_workspace')}
806
- </span>
790
+ <PlusCircle className="size-4" />
791
+ <span>{t('common.create_workspace_action')}</span>
807
792
  </CommandItem>
808
793
  )}
809
794
  <CommandItem
810
- className="justify-center"
795
+ className="h-9 justify-center gap-1.5 px-2"
811
796
  onSelect={() => {
812
797
  setOpen(false);
813
798
  setShowJoinWorkspaceDialog(true);
814
799
  }}
815
800
  >
816
- <Link className="h-5 w-5" />
817
- <span className="truncate">{t('common.join_workspace')}</span>
801
+ <Link className="size-4" />
802
+ <span>{t('common.join_workspace_action')}</span>
818
803
  </CommandItem>
819
804
  </CommandGroup>
820
805
  </Command>
@@ -41,7 +41,7 @@ describe('CategorySpendingChart', () => {
41
41
  vi.clearAllMocks();
42
42
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
43
43
  document.cookie =
44
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
44
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
45
45
  mocks.getCategoryBreakdown.mockResolvedValue([
46
46
  { category_name: 'Food', total: 75 },
47
47
  { category_name: 'Transport', total: 25 },
@@ -50,7 +50,7 @@ describe('CategorySpendingChart', () => {
50
50
 
51
51
  it('masks category percentages when finance numbers are hidden', async () => {
52
52
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
53
- document.cookie = 'finance-confidential-mode=true;path=/';
53
+ document.cookie = 'finance-confidential-mode=true;path=/;Secure';
54
54
 
55
55
  renderCategorySpendingChart();
56
56
 
@@ -63,7 +63,7 @@ describe('CategorySpendingChart', () => {
63
63
 
64
64
  it('shows category percentages when finance numbers are visible', async () => {
65
65
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
66
- document.cookie = 'finance-confidential-mode=false;path=/';
66
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
67
67
 
68
68
  renderCategorySpendingChart();
69
69
 
@@ -40,12 +40,12 @@ describe('BudgetCard', () => {
40
40
  beforeEach(() => {
41
41
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
42
42
  document.cookie =
43
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
43
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
44
44
  });
45
45
 
46
46
  it('masks budget amounts, usage percentage, and progress when finance numbers are hidden', () => {
47
47
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
48
- document.cookie = 'finance-confidential-mode=true;path=/';
48
+ document.cookie = 'finance-confidential-mode=true;path=/;Secure';
49
49
 
50
50
  const { container } = renderBudgetCard();
51
51
 
@@ -58,7 +58,7 @@ describe('BudgetCard', () => {
58
58
 
59
59
  it('shows budget amounts, usage percentage, and progress when finance numbers are visible', () => {
60
60
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
61
- document.cookie = 'finance-confidential-mode=false;path=/';
61
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
62
62
 
63
63
  const { container } = renderBudgetCard();
64
64
 
@@ -40,12 +40,12 @@ describe('DebtLoanCard', () => {
40
40
  beforeEach(() => {
41
41
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
42
42
  document.cookie =
43
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
43
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
44
44
  });
45
45
 
46
46
  it('masks amounts, progress, and interest rate when finance numbers are hidden', () => {
47
47
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
48
- document.cookie = 'finance-confidential-mode=true;path=/';
48
+ document.cookie = 'finance-confidential-mode=true;path=/;Secure';
49
49
 
50
50
  const { container } = renderDebtLoanCard();
51
51
 
@@ -59,7 +59,7 @@ describe('DebtLoanCard', () => {
59
59
 
60
60
  it('shows amounts, progress, and interest rate when finance numbers are visible', () => {
61
61
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
62
- document.cookie = 'finance-confidential-mode=false;path=/';
62
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
63
63
 
64
64
  const { container } = renderDebtLoanCard();
65
65
 
@@ -57,12 +57,12 @@ describe('debt loan detail cards', () => {
57
57
  beforeEach(() => {
58
58
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
59
59
  document.cookie =
60
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
60
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
61
61
  });
62
62
 
63
63
  it('masks payment progress and interest rate when finance numbers are hidden', () => {
64
64
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
65
- document.cookie = 'finance-confidential-mode=true;path=/';
65
+ document.cookie = 'finance-confidential-mode=true;path=/;Secure';
66
66
 
67
67
  const { container } = renderDetailCards();
68
68
 
@@ -76,7 +76,7 @@ describe('debt loan detail cards', () => {
76
76
 
77
77
  it('shows payment progress and interest rate when finance numbers are visible', () => {
78
78
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
79
- document.cookie = 'finance-confidential-mode=false;path=/';
79
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
80
80
 
81
81
  const { container } = renderDetailCards();
82
82
 
@@ -43,12 +43,12 @@ describe('InvoiceTotalsChart', () => {
43
43
  beforeEach(() => {
44
44
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
45
45
  document.cookie =
46
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
46
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
47
47
  });
48
48
 
49
49
  it('masks invoice count totals when finance numbers are hidden', () => {
50
50
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
51
- document.cookie = 'finance-confidential-mode=true;path=/';
51
+ document.cookie = 'finance-confidential-mode=true;path=/;Secure';
52
52
 
53
53
  renderInvoiceTotalsChart();
54
54
 
@@ -58,7 +58,7 @@ describe('InvoiceTotalsChart', () => {
58
58
 
59
59
  it('shows invoice count totals when finance numbers are visible', async () => {
60
60
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
61
- document.cookie = 'finance-confidential-mode=false;path=/';
61
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
62
62
 
63
63
  renderInvoiceTotalsChart();
64
64
 
@@ -41,12 +41,12 @@ describe('invoiceColumns', () => {
41
41
  beforeEach(() => {
42
42
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
43
43
  document.cookie =
44
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
44
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
45
45
  });
46
46
 
47
47
  it('masks amount columns when finance numbers are globally hidden', () => {
48
48
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
49
- document.cookie = 'finance-confidential-mode=true;path=/';
49
+ document.cookie = 'finance-confidential-mode=true;path=/;Secure';
50
50
 
51
51
  render(<InvoicePriceCell price={123} />);
52
52
 
@@ -56,7 +56,7 @@ describe('invoiceColumns', () => {
56
56
 
57
57
  it('shows amount columns when finance numbers are globally visible', async () => {
58
58
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
59
- document.cookie = 'finance-confidential-mode=false;path=/';
59
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
60
60
 
61
61
  render(<InvoicePriceCell price={123} />);
62
62
 
@@ -320,7 +320,7 @@ export function InvoiceCustomerSelectCard({
320
320
  size="sm"
321
321
  className="mx-auto h-8 text-xs"
322
322
  onClick={() => onLoadMore?.()}
323
- disabled={isFetchingNextPage || loading}
323
+ disabled={loading}
324
324
  >
325
325
  {t('ws-invoices.load_more')}
326
326
  </Button>
@@ -10,7 +10,7 @@ describe('FinanceNumbersVisibilityToggle', () => {
10
10
  beforeEach(() => {
11
11
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
12
12
  document.cookie =
13
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
13
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
14
14
  });
15
15
 
16
16
  it('persists visibility state and notifies other finance widgets', () => {
@@ -72,7 +72,7 @@ describe('TagManager', () => {
72
72
  vi.clearAllMocks();
73
73
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
74
74
  document.cookie =
75
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
75
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
76
76
  mocks.listTransactionTags.mockResolvedValue([
77
77
  {
78
78
  id: 'tag-1',
@@ -97,7 +97,7 @@ describe('TagManager', () => {
97
97
 
98
98
  it('renders RPC-backed income, expense, and recent pace stats for each tag', async () => {
99
99
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
100
- document.cookie = 'finance-confidential-mode=false;path=/';
100
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
101
101
 
102
102
  renderTagManager();
103
103
 
@@ -68,12 +68,12 @@ describe('transactionCategoryColumns', () => {
68
68
  beforeEach(() => {
69
69
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
70
70
  document.cookie =
71
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
71
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
72
72
  });
73
73
 
74
74
  it('masks the total amount column when finance numbers are globally hidden', () => {
75
75
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
76
- document.cookie = 'finance-confidential-mode=true;path=/';
76
+ document.cookie = 'finance-confidential-mode=true;path=/;Secure';
77
77
 
78
78
  render(<CategoryAmountCell amount={123} />);
79
79
 
@@ -83,7 +83,7 @@ describe('transactionCategoryColumns', () => {
83
83
 
84
84
  it('shows the total amount column when finance numbers are globally visible', async () => {
85
85
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
86
- document.cookie = 'finance-confidential-mode=false;path=/';
86
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
87
87
 
88
88
  render(<CategoryAmountCell amount={123} />);
89
89
 
@@ -93,7 +93,7 @@ describe('transactionCategoryColumns', () => {
93
93
 
94
94
  it('masks the transaction count column when finance numbers are globally hidden', () => {
95
95
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
96
- document.cookie = 'finance-confidential-mode=true;path=/';
96
+ document.cookie = 'finance-confidential-mode=true;path=/;Secure';
97
97
 
98
98
  render(<CategoryCountCell count={1234} />);
99
99
 
@@ -103,7 +103,7 @@ describe('transactionCategoryColumns', () => {
103
103
 
104
104
  it('shows the transaction count column when finance numbers are globally visible', async () => {
105
105
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
106
- document.cookie = 'finance-confidential-mode=false;path=/';
106
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
107
107
 
108
108
  render(<CategoryCountCell count={1234} />);
109
109
 
@@ -79,7 +79,7 @@ describe('transactionColumns', () => {
79
79
  beforeEach(() => {
80
80
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
81
81
  document.cookie =
82
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
82
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
83
83
  });
84
84
 
85
85
  it('masks the amount column when finance numbers are globally hidden', () => {
@@ -91,7 +91,7 @@ describe('transactionColumns', () => {
91
91
 
92
92
  it('shows the amount column when finance numbers are globally visible', async () => {
93
93
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
94
- document.cookie = 'finance-confidential-mode=false;path=/';
94
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
95
95
 
96
96
  render(<TransactionAmountCell amount={123} />);
97
97
 
@@ -10,7 +10,7 @@ describe('ConfidentialAmount', () => {
10
10
  beforeEach(() => {
11
11
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
12
12
  document.cookie =
13
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
13
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
14
14
  });
15
15
 
16
16
  it('masks visible amounts when finance numbers are globally hidden', () => {
@@ -22,7 +22,7 @@ describe('ConfidentialAmount', () => {
22
22
 
23
23
  it('shows visible amounts when finance numbers are globally shown', () => {
24
24
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
25
- document.cookie = 'finance-confidential-mode=false;path=/';
25
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
26
26
 
27
27
  render(<ConfidentialAmount amount={42} isConfidential={false} />);
28
28
 
@@ -82,7 +82,7 @@ describe('TransactionEditDialog', () => {
82
82
  vi.clearAllMocks();
83
83
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
84
84
  document.cookie =
85
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
85
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
86
86
  mocks.listTransactionCategories.mockResolvedValue([
87
87
  { id: 'category-1', is_expense: false, name: 'Income' },
88
88
  ]);
@@ -95,7 +95,7 @@ describe('TransactionEditDialog', () => {
95
95
 
96
96
  it('masks the summary amount when finance numbers are globally hidden', async () => {
97
97
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
98
- document.cookie = 'finance-confidential-mode=true;path=/';
98
+ document.cookie = 'finance-confidential-mode=true;path=/;Secure';
99
99
 
100
100
  renderDialog();
101
101
 
@@ -24,12 +24,12 @@ describe('TransactionStatistics', () => {
24
24
  beforeEach(() => {
25
25
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
26
26
  document.cookie =
27
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
27
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
28
28
  });
29
29
 
30
30
  it('masks transaction counts and totals when finance numbers are hidden', () => {
31
31
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
32
- document.cookie = 'finance-confidential-mode=true;path=/';
32
+ document.cookie = 'finance-confidential-mode=true;path=/;Secure';
33
33
 
34
34
  renderTransactionStatistics();
35
35
 
@@ -46,7 +46,7 @@ describe('TransactionStatistics', () => {
46
46
 
47
47
  it('shows transaction counts and totals when finance numbers are visible', () => {
48
48
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
49
- document.cookie = 'finance-confidential-mode=false;path=/';
49
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
50
50
 
51
51
  renderTransactionStatistics();
52
52
 
@@ -96,12 +96,12 @@ describe('WalletForm', () => {
96
96
  mocks.updateWallet.mockResolvedValue({ message: 'success' });
97
97
  // biome-ignore lint/suspicious/noDocumentCookie: test resets the finance visibility cookie.
98
98
  document.cookie =
99
- 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
99
+ 'finance-confidential-mode=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Secure';
100
100
  });
101
101
 
102
102
  it('masks the disabled balance field when finance numbers are globally hidden', () => {
103
103
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
104
- document.cookie = 'finance-confidential-mode=true;path=/';
104
+ document.cookie = 'finance-confidential-mode=true;path=/;Secure';
105
105
 
106
106
  renderWalletForm();
107
107
 
@@ -112,7 +112,7 @@ describe('WalletForm', () => {
112
112
 
113
113
  it('shows the disabled balance field when finance numbers are globally visible', async () => {
114
114
  // biome-ignore lint/suspicious/noDocumentCookie: test sets the finance visibility cookie.
115
- document.cookie = 'finance-confidential-mode=false;path=/';
115
+ document.cookie = 'finance-confidential-mode=false;path=/;Secure';
116
116
 
117
117
  renderWalletForm();
118
118