@open-mercato/core 0.6.6-develop.6543.1.49fadb4c31 → 0.6.6-develop.6551.1.1511bfa890

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 (153) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/gateway_session_initialization/index.js +23 -0
  3. package/dist/generated/entities/gateway_session_initialization/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +1 -0
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +12 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/api_keys/api/keys/route.js +7 -4
  9. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  10. package/dist/modules/attachments/components/AttachmentContentPreview.js +6 -35
  11. package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
  12. package/dist/modules/audit_logs/backend/audit-logs/page.js +14 -24
  13. package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
  14. package/dist/modules/auth/api/autologin.js +85 -0
  15. package/dist/modules/auth/api/autologin.js.map +7 -0
  16. package/dist/modules/auth/lib/autologin.js +19 -0
  17. package/dist/modules/auth/lib/autologin.js.map +7 -0
  18. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +1 -0
  19. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
  20. package/dist/modules/customer_accounts/api/admin/roles/[id].js +17 -3
  21. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  22. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +6 -1
  23. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  24. package/dist/modules/customer_accounts/api/portal/users-invite.js +6 -1
  25. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
  26. package/dist/modules/customer_accounts/services/customerInvitationService.js +1 -0
  27. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  28. package/dist/modules/customer_accounts/services/customerRbacService.js +6 -1
  29. package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
  30. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
  31. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
  32. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +27 -37
  33. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  34. package/dist/modules/customers/components/detail/DealDetailTabs.js +23 -35
  35. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
  36. package/dist/modules/customers/components/detail/DetailTabsLayout.js +6 -21
  37. package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
  38. package/dist/modules/customers/components/detail/MobilePersonDetail.js +26 -32
  39. package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
  40. package/dist/modules/customers/components/detail/PersonDetailTabs.js +29 -39
  41. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  42. package/dist/modules/feature_toggles/api/check/boolean/route.js +0 -14
  43. package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
  44. package/dist/modules/feature_toggles/defaults.json +8 -0
  45. package/dist/modules/integrations/backend/integrations/[id]/page.js +9 -80
  46. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  47. package/dist/modules/payment_gateways/data/entities.js +45 -0
  48. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  49. package/dist/modules/payment_gateways/lib/gateway-service.js +173 -38
  50. package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
  51. package/dist/modules/payment_gateways/lib/session-idempotency.js +104 -0
  52. package/dist/modules/payment_gateways/lib/session-idempotency.js.map +7 -0
  53. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +11 -0
  54. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +7 -0
  55. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +16 -14
  56. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
  57. package/dist/modules/planner/components/AvailabilityRulesEditor.js +18 -30
  58. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  59. package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
  60. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  61. package/dist/modules/sales/backend/sales/channels/create/page.js +6 -0
  62. package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
  63. package/dist/modules/sales/backend/sales/channels/create/page.meta.js +2 -0
  64. package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
  65. package/dist/modules/sales/backend/sales/channels/offers/page.js +6 -0
  66. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  67. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +2 -0
  68. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
  69. package/dist/modules/sales/backend/sales/channels/page.js +6 -0
  70. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  71. package/dist/modules/sales/backend/sales/channels/page.meta.js +2 -0
  72. package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
  73. package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -4
  74. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  75. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +15 -0
  76. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +7 -0
  77. package/dist/modules/sales/components/documents/SalesDocumentForm.js +7 -3
  78. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  79. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -8
  80. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  81. package/dist/modules/sales/components/useSalesChannelsEnabled.js +44 -0
  82. package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +7 -0
  83. package/dist/modules/sales/lib/salesChannelsToggle.js +18 -0
  84. package/dist/modules/sales/lib/salesChannelsToggle.js.map +7 -0
  85. package/dist/modules/sales/lib/salesChannelsToggleId.js +5 -0
  86. package/dist/modules/sales/lib/salesChannelsToggleId.js.map +7 -0
  87. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +22 -25
  88. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  89. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -14
  90. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  91. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +10 -29
  92. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
  93. package/dist/modules/sync_excel/lib/adapters/customers.js +19 -20
  94. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  95. package/dist/modules/sync_excel/lib/parser.js +185 -0
  96. package/dist/modules/sync_excel/lib/parser.js.map +2 -2
  97. package/dist/modules/sync_excel/lib/upload-storage.js +20 -1
  98. package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
  99. package/generated/entities/gateway_session_initialization/index.ts +10 -0
  100. package/generated/entities.ids.generated.ts +1 -0
  101. package/generated/entity-fields-registry.ts +12 -0
  102. package/package.json +7 -7
  103. package/src/modules/api_keys/api/keys/route.ts +9 -4
  104. package/src/modules/attachments/components/AttachmentContentPreview.tsx +9 -44
  105. package/src/modules/audit_logs/backend/audit-logs/page.tsx +12 -22
  106. package/src/modules/auth/api/autologin.ts +100 -0
  107. package/src/modules/auth/lib/autologin.ts +36 -0
  108. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +1 -0
  109. package/src/modules/customer_accounts/api/admin/roles/[id].ts +17 -3
  110. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +6 -1
  111. package/src/modules/customer_accounts/api/portal/users-invite.ts +6 -1
  112. package/src/modules/customer_accounts/services/customerInvitationService.ts +1 -0
  113. package/src/modules/customer_accounts/services/customerRbacService.ts +9 -2
  114. package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +6 -2
  115. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +27 -49
  116. package/src/modules/customers/components/detail/DealDetailTabs.tsx +24 -49
  117. package/src/modules/customers/components/detail/DetailTabsLayout.tsx +13 -24
  118. package/src/modules/customers/components/detail/MobilePersonDetail.tsx +25 -31
  119. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +29 -50
  120. package/src/modules/feature_toggles/api/check/boolean/route.ts +0 -15
  121. package/src/modules/feature_toggles/defaults.json +8 -0
  122. package/src/modules/integrations/backend/integrations/[id]/page.tsx +16 -59
  123. package/src/modules/payment_gateways/data/entities.ts +39 -0
  124. package/src/modules/payment_gateways/lib/gateway-service.ts +211 -40
  125. package/src/modules/payment_gateways/lib/session-idempotency.ts +142 -0
  126. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +236 -0
  127. package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +10 -0
  128. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +14 -20
  129. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +16 -34
  130. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +27 -37
  131. package/src/modules/sales/backend/sales/channels/create/page.meta.ts +3 -0
  132. package/src/modules/sales/backend/sales/channels/create/page.tsx +7 -0
  133. package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +3 -0
  134. package/src/modules/sales/backend/sales/channels/offers/page.tsx +7 -0
  135. package/src/modules/sales/backend/sales/channels/page.meta.ts +4 -0
  136. package/src/modules/sales/backend/sales/channels/page.tsx +7 -0
  137. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +7 -5
  138. package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +21 -0
  139. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +7 -3
  140. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +10 -8
  141. package/src/modules/sales/components/useSalesChannelsEnabled.ts +59 -0
  142. package/src/modules/sales/i18n/de.json +1 -0
  143. package/src/modules/sales/i18n/en.json +1 -0
  144. package/src/modules/sales/i18n/es.json +1 -0
  145. package/src/modules/sales/i18n/pl.json +1 -0
  146. package/src/modules/sales/lib/salesChannelsToggle.ts +26 -0
  147. package/src/modules/sales/lib/salesChannelsToggleId.ts +1 -0
  148. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +25 -34
  149. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +11 -17
  150. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +11 -28
  151. package/src/modules/sync_excel/lib/adapters/customers.ts +20 -22
  152. package/src/modules/sync_excel/lib/parser.ts +245 -0
  153. package/src/modules/sync_excel/lib/upload-storage.ts +23 -1
@@ -1,10 +1,9 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import { cn } from '@open-mercato/shared/lib/utils'
5
4
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
5
  import { Button } from '@open-mercato/ui/primitives/button'
7
- import { Badge } from '@open-mercato/ui/primitives/badge'
6
+ import { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'
8
7
  import {
9
8
  Users,
10
9
  Handshake,
@@ -28,7 +27,7 @@ type TabDef = {
28
27
  id: CompanyTabId
29
28
  label: string
30
29
  icon?: React.ReactNode
31
- badge?: React.ReactNode
30
+ count?: React.ReactNode
32
31
  }
33
32
 
34
33
  type CompanyDetailTabsProps = {
@@ -59,21 +58,9 @@ export function resolveLegacyTab(tab: string | null | undefined): CompanyTabId {
59
58
  return tab as CompanyTabId
60
59
  }
61
60
 
62
- function NewBadge() {
63
- return (
64
- <span className="ml-1.5 rounded bg-foreground px-1.5 py-0.5 text-overline font-semibold leading-none text-background">
65
- NEW
66
- </span>
67
- )
68
- }
69
-
70
- function CountBadge({ count }: { count: number }) {
71
- if (count <= 0) return null
72
- return (
73
- <span className="ml-1.5 rounded-full bg-muted px-1.5 py-0.5 text-xs font-medium leading-none text-muted-foreground">
74
- {count > 999 ? '999+' : count}
75
- </span>
76
- )
61
+ function formatTabCount(count: number): string | number | undefined {
62
+ if (count <= 0) return undefined
63
+ return count > 999 ? '999+' : count
77
64
  }
78
65
 
79
66
  export function CompanyDetailTabs({
@@ -96,7 +83,7 @@ export function CompanyDetailTabs({
96
83
  id: 'people',
97
84
  label: t('customers.companies.detail.tabs.people', 'People'),
98
85
  icon: <Users className="size-4" />,
99
- badge: <CountBadge count={peopleCount} />,
86
+ count: formatTabCount(peopleCount),
100
87
  },
101
88
  ...(canViewDeals
102
89
  ? [
@@ -104,7 +91,7 @@ export function CompanyDetailTabs({
104
91
  id: 'deals' as CompanyTabId,
105
92
  label: t('customers.companies.detail.tabs.deals', 'Deals'),
106
93
  icon: <Handshake className="size-4" />,
107
- badge: <CountBadge count={dealsCount} />,
94
+ count: formatTabCount(dealsCount),
108
95
  },
109
96
  ]
110
97
  : []),
@@ -112,19 +99,19 @@ export function CompanyDetailTabs({
112
99
  id: 'activity-log',
113
100
  label: t('customers.companies.detail.tabs.activityLog', 'Activity log'),
114
101
  icon: <Clock className="size-4" />,
115
- badge: <CountBadge count={activitiesCount} />,
102
+ count: formatTabCount(activitiesCount),
116
103
  },
117
104
  {
118
105
  id: 'changelog',
119
106
  label: t('customers.companies.detail.tabs.changelog', 'Changelog'),
120
107
  icon: <History className="size-4" />,
121
- badge: <NewBadge />,
108
+ count: 'NEW',
122
109
  },
123
110
  {
124
111
  id: 'files',
125
112
  label: t('customers.companies.detail.tabs.files', 'Files'),
126
113
  icon: <Paperclip className="size-4" />,
127
- badge: <CountBadge count={filesCount} />,
114
+ count: formatTabCount(filesCount),
128
115
  },
129
116
  ],
130
117
  [t, canViewDeals, peopleCount, dealsCount, activitiesCount, filesCount],
@@ -144,33 +131,24 @@ export function CompanyDetailTabs({
144
131
  return (
145
132
  <div>
146
133
  {/* Tab navigation */}
147
- <div className="flex items-end justify-between gap-2 border-b" role="tablist" aria-label={t('customers.companies.detail.tabs.label', 'Company detail sections')}>
148
- <nav className="-mb-px flex flex-1 gap-1 overflow-x-auto px-1">
149
- {allTabs.map((tab) => {
150
- const isActive = activeTab === tab.id
151
- return (
152
- <Button
153
- key={tab.id}
154
- type="button"
155
- variant="ghost"
156
- size="sm"
157
- role="tab"
158
- aria-selected={isActive}
159
- onClick={() => onTabChange(tab.id)}
160
- className={cn(
161
- 'h-auto shrink-0 rounded-none border-b-2 px-3 py-2.5 hover:bg-transparent',
162
- isActive
163
- ? 'border-foreground text-foreground font-semibold'
164
- : 'border-transparent text-muted-foreground hover:text-foreground',
165
- )}
166
- >
167
- {tab.icon && <span className="mr-1.5">{tab.icon}</span>}
134
+ <div className="flex items-end justify-between gap-2 border-b">
135
+ <Tabs
136
+ value={activeTab}
137
+ onValueChange={(value) => onTabChange(value as CompanyTabId)}
138
+ variant="underline"
139
+ className="min-w-0 flex-1"
140
+ >
141
+ <TabsList
142
+ aria-label={t('customers.companies.detail.tabs.label', 'Company detail sections')}
143
+ className="-mb-px w-full overflow-x-auto border-b-0 px-1"
144
+ >
145
+ {allTabs.map((tab) => (
146
+ <TabsTrigger key={tab.id} value={tab.id} leading={tab.icon} count={tab.count}>
168
147
  {tab.label}
169
- {tab.badge}
170
- </Button>
171
- )
172
- })}
173
- </nav>
148
+ </TabsTrigger>
149
+ ))}
150
+ </TabsList>
151
+ </Tabs>
174
152
  {sectionAction ? (
175
153
  <Button
176
154
  type="button"
@@ -2,9 +2,8 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import { Activity, Building2, History, NotebookPen, Paperclip, Users } from 'lucide-react'
5
- import { cn } from '@open-mercato/shared/lib/utils'
6
5
  import { useT } from '@open-mercato/shared/lib/i18n/context'
7
- import { Button } from '@open-mercato/ui/primitives/button'
6
+ import { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'
8
7
 
9
8
  export type DealTabId =
10
9
  | 'activities'
@@ -19,7 +18,7 @@ type TabDef = {
19
18
  id: DealTabId
20
19
  label: string
21
20
  icon?: React.ReactNode
22
- badge?: React.ReactNode
21
+ count?: React.ReactNode
23
22
  }
24
23
 
25
24
  type DealDetailTabsProps = {
@@ -38,21 +37,9 @@ export function resolveLegacyTab(tab: string | null | undefined): DealTabId {
38
37
  return SUPPORTED_TAB_IDS.has(tab as DealTabId) ? (tab as DealTabId) : 'activities'
39
38
  }
40
39
 
41
- function CountBadge({ count }: { count: number }) {
42
- if (count <= 0) return null
43
- return (
44
- <span className="ml-1.5 rounded-sm bg-muted px-1.5 py-px text-xs font-semibold leading-none text-muted-foreground">
45
- {count > 999 ? '999+' : count}
46
- </span>
47
- )
48
- }
49
-
50
- function NewBadge() {
51
- return (
52
- <span className="ml-1.5 rounded-sm bg-muted px-1.5 py-px text-xs font-semibold leading-none text-muted-foreground">
53
- NEW
54
- </span>
55
- )
40
+ function formatTabCount(count: number): string | number | undefined {
41
+ if (count <= 0) return undefined
42
+ return count > 999 ? '999+' : count
56
43
  }
57
44
 
58
45
  export function DealDetailTabs({
@@ -76,13 +63,13 @@ export function DealDetailTabs({
76
63
  id: 'people',
77
64
  label: t('customers.deals.detail.tabs.people', 'People'),
78
65
  icon: <Users className="size-4" />,
79
- badge: <CountBadge count={peopleCount} />,
66
+ count: formatTabCount(peopleCount),
80
67
  },
81
68
  {
82
69
  id: 'companies',
83
70
  label: t('customers.deals.detail.tabs.companies', 'Companies'),
84
71
  icon: <Building2 className="size-4" />,
85
- badge: <CountBadge count={companiesCount} />,
72
+ count: formatTabCount(companiesCount),
86
73
  },
87
74
  {
88
75
  id: 'notes',
@@ -98,7 +85,7 @@ export function DealDetailTabs({
98
85
  id: 'changelog',
99
86
  label: t('customers.deals.detail.tabs.changelog', 'Changelog'),
100
87
  icon: <History className="size-4" />,
101
- badge: <NewBadge />,
88
+ count: 'NEW',
102
89
  },
103
90
  ],
104
91
  [companiesCount, peopleCount, t],
@@ -117,34 +104,22 @@ export function DealDetailTabs({
117
104
 
118
105
  return (
119
106
  <div>
120
- <div className="border-b border-border" role="tablist" aria-label={t('customers.deals.detail.tabs.label', 'Deal detail sections')}>
121
- <nav className="-mb-px flex gap-7 overflow-x-auto" role="presentation">
122
- {allTabs.map((tab) => {
123
- const isActive = activeTab === tab.id
124
- return (
125
- <Button
126
- key={tab.id}
127
- type="button"
128
- variant="ghost"
129
- size="sm"
130
- role="tab"
131
- aria-selected={isActive}
132
- onClick={() => onTabChange(tab.id)}
133
- className={cn(
134
- 'h-auto shrink-0 rounded-none border-b-2 px-3 py-2.5 hover:bg-transparent',
135
- isActive
136
- ? 'border-foreground text-foreground font-semibold'
137
- : 'border-transparent text-muted-foreground hover:text-foreground',
138
- )}
139
- >
140
- {tab.icon ? <span className="mr-1.5">{tab.icon}</span> : null}
141
- {tab.label}
142
- {tab.badge}
143
- </Button>
144
- )
145
- })}
146
- </nav>
147
- </div>
107
+ <Tabs
108
+ value={activeTab}
109
+ onValueChange={(value) => onTabChange(value as DealTabId)}
110
+ variant="underline"
111
+ >
112
+ <TabsList
113
+ aria-label={t('customers.deals.detail.tabs.label', 'Deal detail sections')}
114
+ className="w-full overflow-x-auto"
115
+ >
116
+ {allTabs.map((tab) => (
117
+ <TabsTrigger key={tab.id} value={tab.id} leading={tab.icon} count={tab.count}>
118
+ {tab.label}
119
+ </TabsTrigger>
120
+ ))}
121
+ </TabsList>
122
+ </Tabs>
148
123
 
149
124
  <div className="pt-5" role="tabpanel">
150
125
  {children}
@@ -3,6 +3,7 @@
3
3
  import * as React from 'react'
4
4
  import type { SectionAction } from '@open-mercato/ui/backend/detail'
5
5
  import { Button } from '@open-mercato/ui/primitives/button'
6
+ import { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'
6
7
  import { cn } from '@open-mercato/shared/lib/utils'
7
8
  import { Plus } from 'lucide-react'
8
9
 
@@ -46,31 +47,19 @@ export function DetailTabsLayout<TId extends string = string>({
46
47
  return (
47
48
  <div className={cn('space-y-4', className)}>
48
49
  <div className={cn('flex flex-wrap items-center justify-between gap-3', headerClassName)}>
49
- <nav
50
- className={cn('flex flex-wrap items-center gap-3 text-sm', navClassName)}
51
- role="tablist"
52
- aria-label={navAriaLabel}
50
+ <Tabs
51
+ value={activeTab}
52
+ onValueChange={(value) => handleTabChange(value as TId)}
53
+ variant="underline"
53
54
  >
54
- {tabs.map((tab) => (
55
- <Button
56
- key={tab.id}
57
- type="button"
58
- variant="ghost"
59
- size="sm"
60
- role="tab"
61
- aria-selected={activeTab === tab.id}
62
- onClick={() => handleTabChange(tab.id)}
63
- className={cn(
64
- 'h-auto rounded-none border-b-2 px-0 py-1',
65
- activeTab === tab.id
66
- ? 'border-accent-indigo text-foreground hover:bg-transparent'
67
- : 'border-transparent text-muted-foreground hover:text-foreground hover:bg-transparent'
68
- )}
69
- >
70
- {tab.label}
71
- </Button>
72
- ))}
73
- </nav>
55
+ <TabsList className={cn('h-auto flex-wrap border-b-0', navClassName)} aria-label={navAriaLabel}>
56
+ {tabs.map((tab) => (
57
+ <TabsTrigger key={tab.id} value={tab.id}>
58
+ {tab.label}
59
+ </TabsTrigger>
60
+ ))}
61
+ </TabsList>
62
+ </Tabs>
74
63
  {sectionAction ? (
75
64
  <Button
76
65
  type="button"
@@ -3,8 +3,7 @@
3
3
  import * as React from 'react'
4
4
  import { useRouter, useSearchParams } from 'next/navigation'
5
5
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
- import { cn } from '@open-mercato/shared/lib/utils'
7
- import { Button } from '@open-mercato/ui/primitives/button'
6
+ import { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'
8
7
 
9
8
  export type MobilePersonZone = 'details' | 'activity'
10
9
 
@@ -80,35 +79,30 @@ export function MobilePersonDetail({
80
79
 
81
80
  return (
82
81
  <div className="space-y-4">
83
- <div
84
- role="tablist"
85
- aria-label={t(
86
- 'customers.people.mobile.zoneSwitcher.ariaLabel',
87
- 'Zone selector',
88
- )}
89
- className="grid grid-cols-2 gap-1 rounded-lg border border-border/70 bg-muted/20 p-1"
90
- onKeyDown={handleKeyDown}
91
- >
92
- {ZONES.map((entry) => {
93
- const isActive = zone === entry.id
94
- return (
95
- <Button
96
- key={entry.id}
97
- type="button"
98
- role="tab"
99
- aria-selected={isActive}
100
- aria-controls={`mobile-person-zone-${entry.id}`}
101
- variant={isActive ? 'default' : 'ghost'}
102
- className={cn(
103
- 'h-11 rounded-lg text-sm font-semibold',
104
- isActive ? '' : 'text-muted-foreground',
105
- )}
106
- onClick={() => handleZoneChange(entry.id)}
107
- >
108
- {t(entry.labelKey, entry.fallback)}
109
- </Button>
110
- )
111
- })}
82
+ <div onKeyDown={handleKeyDown}>
83
+ <Tabs
84
+ value={zone}
85
+ onValueChange={(value) => handleZoneChange(value as MobilePersonZone)}
86
+ variant="underline"
87
+ >
88
+ <TabsList
89
+ aria-label={t(
90
+ 'customers.people.mobile.zoneSwitcher.ariaLabel',
91
+ 'Zone selector',
92
+ )}
93
+ className="w-full"
94
+ >
95
+ {ZONES.map((entry) => (
96
+ <TabsTrigger
97
+ key={entry.id}
98
+ value={entry.id}
99
+ className="flex-1 justify-center"
100
+ >
101
+ {t(entry.labelKey, entry.fallback)}
102
+ </TabsTrigger>
103
+ ))}
104
+ </TabsList>
105
+ </Tabs>
112
106
  </div>
113
107
  <div
114
108
  id={`mobile-person-zone-${zone}`}
@@ -1,9 +1,9 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import { cn } from '@open-mercato/shared/lib/utils'
5
4
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
5
  import { Button } from '@open-mercato/ui/primitives/button'
6
+ import { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'
7
7
  import {
8
8
  SquareCheckBig,
9
9
  Mail,
@@ -32,7 +32,7 @@ type TabDef = {
32
32
  id: PersonTabId
33
33
  label: string
34
34
  icon?: React.ReactNode
35
- badge?: React.ReactNode
35
+ count?: React.ReactNode
36
36
  }
37
37
 
38
38
  type PersonDetailTabsProps = {
@@ -56,21 +56,9 @@ export function resolveLegacyTab(tab: string | null | undefined): PersonTabId {
56
56
  return SUPPORTED_TAB_IDS.has(tab as PersonTabId) ? (tab as PersonTabId) : 'activities'
57
57
  }
58
58
 
59
- function CountBadge({ count }: { count: number }) {
60
- if (count <= 0) return null
61
- return (
62
- <span className="ml-1 rounded-full bg-muted px-1.5 py-0.5 text-xs font-medium leading-none text-muted-foreground">
63
- {count > 999 ? '999+' : count}
64
- </span>
65
- )
66
- }
67
-
68
- function NewBadge() {
69
- return (
70
- <span className="ml-1.5 rounded bg-foreground px-1.5 py-0.5 text-overline font-semibold leading-none text-background">
71
- NEW
72
- </span>
73
- )
59
+ function formatTabCount(count: number): string | number | undefined {
60
+ if (count <= 0) return undefined
61
+ return count > 999 ? '999+' : count
74
62
  }
75
63
 
76
64
  export function PersonDetailTabs({
@@ -94,7 +82,7 @@ export function PersonDetailTabs({
94
82
  id: 'activities',
95
83
  label: t('customers.people.detail.tabs.activities', 'Activities'),
96
84
  icon: <SquareCheckBig className="size-4" />,
97
- badge: <CountBadge count={activitiesCount} />,
85
+ count: formatTabCount(activitiesCount),
98
86
  },
99
87
  {
100
88
  id: 'emails',
@@ -105,37 +93,37 @@ export function PersonDetailTabs({
105
93
  id: 'deals',
106
94
  label: t('customers.people.detail.tabs.deals', 'Deals'),
107
95
  icon: <Briefcase className="size-4" />,
108
- badge: <CountBadge count={dealsCount} />,
96
+ count: formatTabCount(dealsCount),
109
97
  },
110
98
  {
111
99
  id: 'companies',
112
100
  label: t('customers.people.detail.tabs.companies', 'Companies'),
113
101
  icon: <Building2 className="size-4" />,
114
- badge: <CountBadge count={companiesCount} />,
102
+ count: formatTabCount(companiesCount),
115
103
  },
116
104
  {
117
105
  id: 'addresses',
118
106
  label: t('customers.people.detail.tabs.addresses', 'Addresses'),
119
107
  icon: <MapPin className="size-4" />,
120
- badge: <CountBadge count={addressesCount} />,
108
+ count: formatTabCount(addressesCount),
121
109
  },
122
110
  {
123
111
  id: 'tasks',
124
112
  label: t('customers.people.detail.tabs.tasks', 'Tasks'),
125
113
  icon: <Check className="size-4" />,
126
- badge: <CountBadge count={tasksCount} />,
114
+ count: formatTabCount(tasksCount),
127
115
  },
128
116
  {
129
117
  id: 'changelog',
130
118
  label: t('customers.people.detail.tabs.changelog', 'Change log'),
131
119
  icon: <History className="size-4" />,
132
- badge: <NewBadge />,
120
+ count: 'NEW',
133
121
  },
134
122
  {
135
123
  id: 'files',
136
124
  label: t('customers.people.detail.tabs.files', 'Files'),
137
125
  icon: <Paperclip className="size-4" />,
138
- badge: <CountBadge count={filesCount} />,
126
+ count: formatTabCount(filesCount),
139
127
  },
140
128
  ],
141
129
  [t, activitiesCount, dealsCount, companiesCount, addressesCount, tasksCount, filesCount],
@@ -155,33 +143,24 @@ export function PersonDetailTabs({
155
143
  return (
156
144
  <div>
157
145
  {/* Tab navigation — full width above both zones */}
158
- <div className="flex items-end justify-between gap-2 border-b" role="tablist" aria-label={t('customers.people.detail.tabs.label', 'Person detail sections')}>
159
- <nav className="-mb-px flex flex-1 gap-1 overflow-x-auto px-1">
160
- {allTabs.map((tab) => {
161
- const isActive = activeTab === tab.id
162
- return (
163
- <Button
164
- key={tab.id}
165
- type="button"
166
- variant="ghost"
167
- size="sm"
168
- role="tab"
169
- aria-selected={isActive}
170
- onClick={() => onTabChange(tab.id)}
171
- className={cn(
172
- 'h-auto shrink-0 rounded-none border-b-2 px-3 py-2.5 hover:bg-transparent',
173
- isActive
174
- ? 'border-foreground text-foreground font-semibold'
175
- : 'border-transparent text-muted-foreground hover:text-foreground',
176
- )}
177
- >
178
- {tab.icon && <span className="mr-1.5">{tab.icon}</span>}
146
+ <div className="flex items-end justify-between gap-2 border-b">
147
+ <Tabs
148
+ value={activeTab}
149
+ onValueChange={(value) => onTabChange(value as PersonTabId)}
150
+ variant="underline"
151
+ className="min-w-0 flex-1"
152
+ >
153
+ <TabsList
154
+ aria-label={t('customers.people.detail.tabs.label', 'Person detail sections')}
155
+ className="-mb-px w-full overflow-x-auto border-b-0 px-1"
156
+ >
157
+ {allTabs.map((tab) => (
158
+ <TabsTrigger key={tab.id} value={tab.id} leading={tab.icon} count={tab.count}>
179
159
  {tab.label}
180
- {tab.badge}
181
- </Button>
182
- )
183
- })}
184
- </nav>
160
+ </TabsTrigger>
161
+ ))}
162
+ </TabsList>
163
+ </Tabs>
185
164
  {sectionAction ? (
186
165
  <Button
187
166
  type="button"
@@ -18,21 +18,6 @@ export async function GET(req: Request) {
18
18
  return NextResponse.json({ error: 'Missing required parameter: identifier' }, { status: 400 })
19
19
  }
20
20
 
21
- if (auth.isSuperAdmin === true) {
22
- return NextResponse.json({
23
- ok: true,
24
- value: true,
25
- resolution: {
26
- valueType: "boolean",
27
- value: true,
28
- source: "override",
29
- toggleId: "superadmin",
30
- identifier,
31
- tenantId: "superadmin"
32
- }
33
- })
34
- }
35
-
36
21
  const container = await createRequestContainer()
37
22
  const { scope } = await resolveFeatureCheckContext({
38
23
  container,
@@ -16,6 +16,14 @@
16
16
  "type": "string",
17
17
  "defaultValue": "Free shipping on orders over $50"
18
18
  },
19
+ {
20
+ "identifier": "sales_channels_enabled",
21
+ "name": "Sales Channels",
22
+ "description": "Show sales channel pickers, filters, and management UI. Disable for tenants that do not use sales channels.",
23
+ "category": "sales",
24
+ "type": "boolean",
25
+ "defaultValue": true
26
+ },
19
27
  {
20
28
  "identifier": "order_review_threshold",
21
29
  "name": "Order Review Threshold",