@open-mercato/core 0.6.1-develop.3276.1.1fcd145078 → 0.6.1-develop.3287.1.450f4ffb56

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 (266) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/domain_mapping/index.js +31 -0
  3. package/dist/generated/entities/domain_mapping/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +2 -1
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +16 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/helpers/integration/auth.js +1 -1
  9. package/dist/helpers/integration/auth.js.map +2 -2
  10. package/dist/helpers/integration/authUi.js +1 -1
  11. package/dist/helpers/integration/authUi.js.map +2 -2
  12. package/dist/helpers/integration/salesUi.js +18 -17
  13. package/dist/helpers/integration/salesUi.js.map +2 -2
  14. package/dist/modules/auth/frontend/login.js +4 -4
  15. package/dist/modules/auth/frontend/login.js.map +2 -2
  16. package/dist/modules/auth/frontend/reset/[token]/page.js +5 -5
  17. package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
  18. package/dist/modules/auth/frontend/reset.js +2 -2
  19. package/dist/modules/auth/frontend/reset.js.map +2 -2
  20. package/dist/modules/customer_accounts/acl.js +2 -1
  21. package/dist/modules/customer_accounts/acl.js.map +2 -2
  22. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js +110 -0
  23. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js.map +7 -0
  24. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js +114 -0
  25. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js.map +7 -0
  26. package/dist/modules/customer_accounts/api/admin/domain-mappings.js +271 -0
  27. package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +7 -0
  28. package/dist/modules/customer_accounts/api/domain-check.js +70 -0
  29. package/dist/modules/customer_accounts/api/domain-check.js.map +7 -0
  30. package/dist/modules/customer_accounts/api/domain-resolve/all.js +128 -0
  31. package/dist/modules/customer_accounts/api/domain-resolve/all.js.map +7 -0
  32. package/dist/modules/customer_accounts/api/domain-resolve.js +84 -0
  33. package/dist/modules/customer_accounts/api/domain-resolve.js.map +7 -0
  34. package/dist/modules/customer_accounts/api/login.js +14 -3
  35. package/dist/modules/customer_accounts/api/login.js.map +2 -2
  36. package/dist/modules/customer_accounts/api/magic-link/request.js +14 -3
  37. package/dist/modules/customer_accounts/api/magic-link/request.js.map +2 -2
  38. package/dist/modules/customer_accounts/api/password/reset-request.js +14 -3
  39. package/dist/modules/customer_accounts/api/password/reset-request.js.map +2 -2
  40. package/dist/modules/customer_accounts/api/signup.js +32 -5
  41. package/dist/modules/customer_accounts/api/signup.js.map +2 -2
  42. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js +48 -0
  43. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js.map +7 -0
  44. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +43 -0
  45. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +7 -0
  46. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js +53 -0
  47. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js.map +7 -0
  48. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js +69 -0
  49. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js.map +7 -0
  50. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js +31 -0
  51. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js.map +7 -0
  52. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js +147 -0
  53. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js.map +7 -0
  54. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js +39 -0
  55. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js.map +7 -0
  56. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js +60 -0
  57. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js.map +7 -0
  58. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js +11 -0
  59. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js.map +7 -0
  60. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js +1 -0
  61. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js.map +7 -0
  62. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +361 -0
  63. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +7 -0
  64. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js +28 -0
  65. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js.map +7 -0
  66. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +4 -3
  67. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  68. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +6 -5
  69. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  70. package/dist/modules/customer_accounts/data/enrichers.js +78 -12
  71. package/dist/modules/customer_accounts/data/enrichers.js.map +2 -2
  72. package/dist/modules/customer_accounts/data/entities.js +85 -2
  73. package/dist/modules/customer_accounts/data/entities.js.map +2 -2
  74. package/dist/modules/customer_accounts/data/guards.js +130 -0
  75. package/dist/modules/customer_accounts/data/guards.js.map +7 -0
  76. package/dist/modules/customer_accounts/data/validators.js +19 -0
  77. package/dist/modules/customer_accounts/data/validators.js.map +2 -2
  78. package/dist/modules/customer_accounts/di.js +23 -1
  79. package/dist/modules/customer_accounts/di.js.map +2 -2
  80. package/dist/modules/customer_accounts/events.js +9 -2
  81. package/dist/modules/customer_accounts/events.js.map +2 -2
  82. package/dist/modules/customer_accounts/lib/customerAuthServer.js +27 -7
  83. package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +2 -2
  84. package/dist/modules/customer_accounts/lib/customerUrl.js +48 -0
  85. package/dist/modules/customer_accounts/lib/customerUrl.js.map +7 -0
  86. package/dist/modules/customer_accounts/lib/hostname.js +55 -0
  87. package/dist/modules/customer_accounts/lib/hostname.js.map +7 -0
  88. package/dist/modules/customer_accounts/lib/platformDomains.js +7 -0
  89. package/dist/modules/customer_accounts/lib/platformDomains.js.map +7 -0
  90. package/dist/modules/customer_accounts/lib/proxyRanges.js +71 -0
  91. package/dist/modules/customer_accounts/lib/proxyRanges.js.map +7 -0
  92. package/dist/modules/customer_accounts/lib/rateLimiter.js +7 -0
  93. package/dist/modules/customer_accounts/lib/rateLimiter.js.map +2 -2
  94. package/dist/modules/customer_accounts/lib/resolveTenantContext.js +63 -0
  95. package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +7 -0
  96. package/dist/modules/customer_accounts/lib/secretCompare.js +13 -0
  97. package/dist/modules/customer_accounts/lib/secretCompare.js.map +7 -0
  98. package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js +37 -0
  99. package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js.map +7 -0
  100. package/dist/modules/customer_accounts/notifications.client.js +76 -0
  101. package/dist/modules/customer_accounts/notifications.client.js.map +2 -2
  102. package/dist/modules/customer_accounts/notifications.js +76 -0
  103. package/dist/modules/customer_accounts/notifications.js.map +2 -2
  104. package/dist/modules/customer_accounts/services/domainMappingService.js +497 -0
  105. package/dist/modules/customer_accounts/services/domainMappingService.js.map +7 -0
  106. package/dist/modules/customer_accounts/setup.js +48 -1
  107. package/dist/modules/customer_accounts/setup.js.map +2 -2
  108. package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js +29 -0
  109. package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js.map +7 -0
  110. package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js +29 -0
  111. package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js.map +7 -0
  112. package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js +30 -0
  113. package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js.map +7 -0
  114. package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js +31 -0
  115. package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js.map +7 -0
  116. package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js +29 -0
  117. package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js.map +7 -0
  118. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -2
  119. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  120. package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js +67 -0
  121. package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js.map +7 -0
  122. package/dist/modules/customer_accounts/workers/domainVerificationWorker.js +34 -0
  123. package/dist/modules/customer_accounts/workers/domainVerificationWorker.js.map +7 -0
  124. package/dist/modules/customers/api/companies/route.js +4 -9
  125. package/dist/modules/customers/api/companies/route.js.map +2 -2
  126. package/dist/modules/customers/api/deals/route.js +4 -9
  127. package/dist/modules/customers/api/deals/route.js.map +2 -2
  128. package/dist/modules/customers/api/people/route.js +4 -9
  129. package/dist/modules/customers/api/people/route.js.map +2 -2
  130. package/dist/modules/customers/api/utils.js +0 -12
  131. package/dist/modules/customers/api/utils.js.map +2 -2
  132. package/dist/modules/customers/backend/customers/companies/page.js +361 -284
  133. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  134. package/dist/modules/customers/backend/customers/deals/page.js +378 -473
  135. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  136. package/dist/modules/customers/backend/customers/people/page.js +368 -286
  137. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  138. package/dist/modules/customers/components/detail/assignableStaff.js +15 -1
  139. package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
  140. package/dist/modules/customers/components/formConfig.js +2 -2
  141. package/dist/modules/customers/components/formConfig.js.map +2 -2
  142. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +188 -36
  143. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
  144. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +2 -2
  145. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +2 -2
  146. package/dist/modules/customers/widgets/injection-table.js +6 -0
  147. package/dist/modules/customers/widgets/injection-table.js.map +2 -2
  148. package/dist/modules/integrations/backend/integrations/[id]/page.js +2 -3
  149. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  150. package/dist/modules/perspectives/services/perspectiveService.js +16 -2
  151. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  152. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +5 -3
  153. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
  154. package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js +3 -3
  155. package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js.map +2 -2
  156. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +4 -2
  157. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +2 -2
  158. package/dist/modules/query_index/lib/engine.js +31 -8
  159. package/dist/modules/query_index/lib/engine.js.map +2 -2
  160. package/dist/modules/sales/backend/sales/documents/[id]/page.js +3 -5
  161. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  162. package/dist/modules/sales/components/ProviderFieldInput.js +2 -2
  163. package/dist/modules/sales/components/ProviderFieldInput.js.map +2 -2
  164. package/dist/modules/sales/components/documents/SalesDocumentForm.js +3 -4
  165. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  166. package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js +3 -4
  167. package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js.map +2 -2
  168. package/dist/modules/workflows/backend/tasks/[id]/page.js +2 -2
  169. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  170. package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -2
  171. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  172. package/generated/entities/domain_mapping/index.ts +14 -0
  173. package/generated/entities.ids.generated.ts +2 -1
  174. package/generated/entity-fields-registry.ts +16 -0
  175. package/package.json +5 -5
  176. package/src/helpers/integration/auth.ts +1 -1
  177. package/src/helpers/integration/authUi.ts +1 -1
  178. package/src/helpers/integration/salesUi.ts +27 -23
  179. package/src/modules/auth/frontend/login.tsx +4 -3
  180. package/src/modules/auth/frontend/reset/[token]/page.tsx +4 -4
  181. package/src/modules/auth/frontend/reset.tsx +2 -2
  182. package/src/modules/customer_accounts/acl.ts +1 -0
  183. package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.ts +119 -0
  184. package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.ts +123 -0
  185. package/src/modules/customer_accounts/api/admin/domain-mappings.ts +303 -0
  186. package/src/modules/customer_accounts/api/domain-check.ts +83 -0
  187. package/src/modules/customer_accounts/api/domain-resolve/all.ts +139 -0
  188. package/src/modules/customer_accounts/api/domain-resolve.ts +91 -0
  189. package/src/modules/customer_accounts/api/login.ts +14 -3
  190. package/src/modules/customer_accounts/api/magic-link/request.ts +14 -3
  191. package/src/modules/customer_accounts/api/password/reset-request.ts +15 -3
  192. package/src/modules/customer_accounts/api/signup.ts +36 -5
  193. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.tsx +70 -0
  194. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +66 -0
  195. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.tsx +78 -0
  196. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.tsx +90 -0
  197. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.tsx +43 -0
  198. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.tsx +178 -0
  199. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.tsx +56 -0
  200. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.tsx +69 -0
  201. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.ts +10 -0
  202. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.ts +31 -0
  203. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.ts +26 -0
  204. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +411 -0
  205. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +3 -2
  206. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +5 -4
  207. package/src/modules/customer_accounts/data/enrichers.ts +95 -12
  208. package/src/modules/customer_accounts/data/entities.ts +80 -1
  209. package/src/modules/customer_accounts/data/guards.ts +155 -0
  210. package/src/modules/customer_accounts/data/validators.ts +26 -0
  211. package/src/modules/customer_accounts/di.ts +35 -1
  212. package/src/modules/customer_accounts/events.ts +8 -1
  213. package/src/modules/customer_accounts/i18n/de.json +78 -0
  214. package/src/modules/customer_accounts/i18n/en.json +78 -0
  215. package/src/modules/customer_accounts/i18n/es.json +78 -0
  216. package/src/modules/customer_accounts/i18n/pl.json +78 -0
  217. package/src/modules/customer_accounts/lib/customerAuthServer.ts +44 -8
  218. package/src/modules/customer_accounts/lib/customerUrl.ts +72 -0
  219. package/src/modules/customer_accounts/lib/hostname.ts +76 -0
  220. package/src/modules/customer_accounts/lib/platformDomains.ts +14 -0
  221. package/src/modules/customer_accounts/lib/proxyRanges.ts +85 -0
  222. package/src/modules/customer_accounts/lib/rateLimiter.ts +10 -0
  223. package/src/modules/customer_accounts/lib/resolveTenantContext.ts +96 -0
  224. package/src/modules/customer_accounts/lib/secretCompare.ts +20 -0
  225. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +341 -0
  226. package/src/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.ts +38 -0
  227. package/src/modules/customer_accounts/notifications.client.ts +76 -0
  228. package/src/modules/customer_accounts/notifications.ts +76 -0
  229. package/src/modules/customer_accounts/services/domainMappingService.ts +632 -0
  230. package/src/modules/customer_accounts/setup.ts +81 -1
  231. package/src/modules/customer_accounts/subscribers/invalidateDomainCache.ts +35 -0
  232. package/src/modules/customer_accounts/subscribers/notifyDomainActivated.ts +32 -0
  233. package/src/modules/customer_accounts/subscribers/notifyDomainDnsFailed.ts +33 -0
  234. package/src/modules/customer_accounts/subscribers/notifyDomainTlsFailed.ts +34 -0
  235. package/src/modules/customer_accounts/subscribers/notifyDomainVerified.ts +32 -0
  236. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -2
  237. package/src/modules/customer_accounts/workers/domainTlsRetryWorker.ts +84 -0
  238. package/src/modules/customer_accounts/workers/domainVerificationWorker.ts +45 -0
  239. package/src/modules/customers/api/companies/route.ts +4 -9
  240. package/src/modules/customers/api/deals/route.ts +4 -9
  241. package/src/modules/customers/api/people/route.ts +4 -9
  242. package/src/modules/customers/api/utils.ts +0 -14
  243. package/src/modules/customers/backend/customers/companies/page.tsx +321 -237
  244. package/src/modules/customers/backend/customers/deals/page.tsx +321 -455
  245. package/src/modules/customers/backend/customers/people/page.tsx +331 -240
  246. package/src/modules/customers/components/detail/assignableStaff.ts +21 -0
  247. package/src/modules/customers/components/formConfig.tsx +2 -2
  248. package/src/modules/customers/i18n/de.json +31 -0
  249. package/src/modules/customers/i18n/en.json +31 -0
  250. package/src/modules/customers/i18n/es.json +31 -0
  251. package/src/modules/customers/i18n/pl.json +31 -0
  252. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +197 -40
  253. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +13 -7
  254. package/src/modules/customers/widgets/injection-table.ts +6 -0
  255. package/src/modules/integrations/backend/integrations/[id]/page.tsx +2 -2
  256. package/src/modules/perspectives/services/perspectiveService.ts +30 -2
  257. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +5 -3
  258. package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.tsx +3 -3
  259. package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +4 -2
  260. package/src/modules/query_index/lib/engine.ts +46 -8
  261. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +3 -5
  262. package/src/modules/sales/components/ProviderFieldInput.tsx +2 -2
  263. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +2 -3
  264. package/src/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.tsx +3 -4
  265. package/src/modules/workflows/backend/tasks/[id]/page.tsx +2 -2
  266. package/src/modules/workflows/frontend/checkout-demo/page.tsx +2 -2
@@ -2,6 +2,10 @@ import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
4
4
  import { passwordResetRequestSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
5
+ import {
6
+ resolveTenantContext,
7
+ TenantResolutionError,
8
+ } from '@open-mercato/core/modules/customer_accounts/lib/resolveTenantContext'
5
9
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
10
  import { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'
7
11
  import { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'
@@ -37,9 +41,17 @@ export async function POST(req: Request) {
37
41
  return NextResponse.json({ ok: true }) // Always 200 to prevent enumeration
38
42
  }
39
43
 
40
- const { email, tenantId } = parsed.data
41
- if (!tenantId) {
42
- return NextResponse.json({ ok: true })
44
+ const { email } = parsed.data
45
+ let tenantId: string
46
+ try {
47
+ const context = await resolveTenantContext(req, parsed.data.tenantId)
48
+ tenantId = context.tenantId
49
+ } catch (err) {
50
+ if (err instanceof TenantResolutionError) {
51
+ // Always return 200 to avoid leaking whether a tenant exists for this host.
52
+ return NextResponse.json({ ok: true })
53
+ }
54
+ throw err
43
55
  }
44
56
 
45
57
  const container = await createRequestContainer()
@@ -21,6 +21,11 @@ import {
21
21
  import { readNormalizedEmailFromJsonRequest } from '@open-mercato/core/modules/customer_accounts/lib/rateLimitIdentifier'
22
22
  import { findOrganizationInTenant } from '@open-mercato/core/modules/customer_accounts/lib/organizationLookup'
23
23
  import { getSecurityEmailBaseUrl, mapSecurityEmailUrlError } from '@open-mercato/shared/lib/url'
24
+ import {
25
+ resolveTenantContext,
26
+ TenantResolutionError,
27
+ } from '@open-mercato/core/modules/customer_accounts/lib/resolveTenantContext'
28
+ import { urlForCustomerOrg } from '@open-mercato/core/modules/customer_accounts/lib/customerUrl'
24
29
 
25
30
  export const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }
26
31
 
@@ -63,9 +68,21 @@ export async function POST(req: Request) {
63
68
  return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })
64
69
  }
65
70
 
66
- const { email, password, displayName, tenantId, organizationId } = parsed.data
67
- if (!tenantId || !organizationId) {
68
- return NextResponse.json({ ok: false, error: 'tenantId and organizationId are required' }, { status: 400 })
71
+ const { email, password, displayName } = parsed.data
72
+ let tenantId: string
73
+ let organizationId: string | null
74
+ try {
75
+ const context = await resolveTenantContext(req, parsed.data.tenantId)
76
+ tenantId = context.tenantId
77
+ organizationId = context.organizationId ?? parsed.data.organizationId ?? null
78
+ } catch (err) {
79
+ if (err instanceof TenantResolutionError) {
80
+ return NextResponse.json({ ok: false, error: err.message }, { status: err.status })
81
+ }
82
+ throw err
83
+ }
84
+ if (!organizationId) {
85
+ return NextResponse.json({ ok: false, error: 'organizationId is required' }, { status: 400 })
69
86
  }
70
87
 
71
88
  let baseUrl: string
@@ -95,7 +112,14 @@ export async function POST(req: Request) {
95
112
  if (existing) {
96
113
  await bcryptCompare(password, TIMING_EQUALIZATION_HASH)
97
114
  const existingOrg = await findOrganizationInTenant(em, existing.organizationId, tenantId)
98
- const loginUrl = resolvePortalLoginUrl(baseUrl, existingOrg?.slug ?? null)
115
+ // Prefer the org's active custom domain when available; fall back to the
116
+ // platform login URL preserved by `resolvePortalLoginUrl`.
117
+ let loginUrl = resolvePortalLoginUrl(baseUrl, existingOrg?.slug ?? null)
118
+ try {
119
+ loginUrl = await urlForCustomerOrg(existing.organizationId, '/login', { container })
120
+ } catch {
121
+ // Fall back to platform URL on any resolution issue.
122
+ }
99
123
  const subject = translate('customer_accounts.signup.existing.subject', 'You already have a portal account')
100
124
  const copy = {
101
125
  preview: translate('customer_accounts.signup.existing.preview', 'A sign-up attempt was made for an email that already has a portal account.'),
@@ -141,7 +165,14 @@ export async function POST(req: Request) {
141
165
  await em.persist(user).flush()
142
166
 
143
167
  const verificationToken = await customerTokenService.createEmailVerification(user.id, tenantId)
144
- const verifyUrl = resolvePortalVerifyUrl(baseUrl, verificationToken, orgRow.slug)
168
+ let verifyUrl = resolvePortalVerifyUrl(baseUrl, verificationToken, orgRow.slug)
169
+ try {
170
+ verifyUrl = await urlForCustomerOrg(organizationId, `/verify?token=${encodeURIComponent(verificationToken)}`, {
171
+ container,
172
+ })
173
+ } catch {
174
+ // Fall back to the platform-derived URL on any resolution issue.
175
+ }
145
176
  const subject = translate('customer_accounts.signup.created.subject', 'Verify your portal account')
146
177
  const copy = {
147
178
  preview: translate('customer_accounts.signup.created.preview', 'Verify your portal account to finish sign-up.'),
@@ -0,0 +1,70 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { ExternalLink, RefreshCw, Replace, Trash2 } from 'lucide-react'
5
+ import { Button } from '@open-mercato/ui/primitives/button'
6
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
7
+ import type { DomainMappingRow } from './types'
8
+
9
+ export type ActionsProps = {
10
+ mapping: DomainMappingRow
11
+ isReplacement: boolean
12
+ onCheckNow: () => void
13
+ onRetryTls: () => void
14
+ onChangeDomain: () => void
15
+ onRemove: () => void
16
+ busy?: boolean
17
+ }
18
+
19
+ export function DefaultActions({
20
+ mapping,
21
+ isReplacement,
22
+ onCheckNow,
23
+ onRetryTls,
24
+ onChangeDomain,
25
+ onRemove,
26
+ busy,
27
+ }: ActionsProps) {
28
+ const t = useT()
29
+ const showCheckNow = mapping.status === 'pending' || mapping.status === 'dns_failed'
30
+ const showRetryTls = mapping.status === 'tls_failed' || mapping.status === 'verified'
31
+ const showTestDomain = mapping.status === 'active'
32
+ const showChangeDomain = mapping.status === 'active' && !isReplacement
33
+
34
+ return (
35
+ <div className="flex flex-wrap items-center gap-2">
36
+ {showCheckNow ? (
37
+ <Button type="button" variant="outline" size="sm" onClick={onCheckNow} disabled={busy}>
38
+ <RefreshCw className="mr-1.5 h-4 w-4" aria-hidden />
39
+ {t('customer_accounts.domainMapping.verifyNow', 'Check Now')}
40
+ </Button>
41
+ ) : null}
42
+ {showRetryTls ? (
43
+ <Button type="button" variant="outline" size="sm" onClick={onRetryTls} disabled={busy}>
44
+ <RefreshCw className="mr-1.5 h-4 w-4" aria-hidden />
45
+ {t('customer_accounts.domainMapping.tls.retryButton', 'Retry SSL')}
46
+ </Button>
47
+ ) : null}
48
+ {showTestDomain ? (
49
+ <Button type="button" variant="outline" size="sm" asChild>
50
+ <a href={`https://${mapping.hostname}`} target="_blank" rel="noopener noreferrer">
51
+ <ExternalLink className="mr-1.5 h-4 w-4" aria-hidden />
52
+ {t('customer_accounts.domainMapping.preview', 'Test Domain')}
53
+ </a>
54
+ </Button>
55
+ ) : null}
56
+ {showChangeDomain ? (
57
+ <Button type="button" variant="outline" size="sm" onClick={onChangeDomain} disabled={busy}>
58
+ <Replace className="mr-1.5 h-4 w-4" aria-hidden />
59
+ {t('customer_accounts.domainMapping.changeDomain', 'Change Domain')}
60
+ </Button>
61
+ ) : null}
62
+ <Button type="button" variant="outline" size="sm" onClick={onRemove} disabled={busy}>
63
+ <Trash2 className="mr-1.5 h-4 w-4" aria-hidden />
64
+ {t('customer_accounts.domainMapping.removeDomain', 'Remove Domain')}
65
+ </Button>
66
+ </div>
67
+ )
68
+ }
69
+
70
+ export default DefaultActions
@@ -0,0 +1,66 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { AlertTriangle } from 'lucide-react'
5
+ import { Alert, AlertTitle, AlertDescription } from '@open-mercato/ui/primitives/alert'
6
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
7
+ import type { DomainMappingRow } from './types'
8
+
9
+ export type DiagnosticsProps = {
10
+ mapping: DomainMappingRow
11
+ }
12
+
13
+ export function DnsDiagnostics({ mapping }: DiagnosticsProps) {
14
+ const t = useT()
15
+ if (mapping.status !== 'dns_failed') return null
16
+ return (
17
+ <Alert variant="destructive">
18
+ <AlertTriangle className="h-4 w-4" aria-hidden />
19
+ <AlertTitle>
20
+ {t('customer_accounts.domainMapping.dns.diagnostics.title', 'DNS configuration issue')}
21
+ </AlertTitle>
22
+ <AlertDescription>
23
+ <div className="space-y-2">
24
+ {mapping.dnsFailureReason ? <p>{mapping.dnsFailureReason}</p> : null}
25
+ {mapping.cnameTarget ? (
26
+ <dl className="grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1 text-xs">
27
+ <dt className="text-muted-foreground">
28
+ {t('customer_accounts.domainMapping.dns.diagnostics.expected', 'Expected target')}
29
+ </dt>
30
+ <dd className="font-mono">{mapping.cnameTarget}</dd>
31
+ </dl>
32
+ ) : null}
33
+ </div>
34
+ </AlertDescription>
35
+ </Alert>
36
+ )
37
+ }
38
+
39
+ export function TlsDiagnostics({ mapping }: DiagnosticsProps) {
40
+ const t = useT()
41
+ if (mapping.status !== 'tls_failed') return null
42
+ return (
43
+ <Alert variant="warning">
44
+ <AlertTriangle className="h-4 w-4" aria-hidden />
45
+ <AlertTitle>
46
+ {t('customer_accounts.domainMapping.tls.diagnostics.title', 'SSL certificate issue')}
47
+ </AlertTitle>
48
+ <AlertDescription>
49
+ <div className="space-y-2">
50
+ {mapping.tlsFailureReason ? <p>{mapping.tlsFailureReason}</p> : null}
51
+ <p className="text-xs text-muted-foreground">
52
+ {t('customer_accounts.domainMapping.tls.diagnostics.retryCount', 'Retry attempts: {count}', {
53
+ count: String(mapping.tlsRetryCount),
54
+ })}
55
+ </p>
56
+ <p className="text-xs text-muted-foreground">
57
+ {t(
58
+ 'customer_accounts.domainMapping.tls.diagnostics.operatorNote',
59
+ 'We are retrying automatically. If this persists, contact platform support — your DNS is fine, this is on our side.',
60
+ )}
61
+ </p>
62
+ </div>
63
+ </AlertDescription>
64
+ </Alert>
65
+ )
66
+ }
@@ -0,0 +1,78 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { Copy, Check } from 'lucide-react'
5
+ import { Button } from '@open-mercato/ui/primitives/button'
6
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
7
+
8
+ export type DnsConfigProps = {
9
+ hostname: string | null
10
+ cnameTarget: string | null
11
+ aRecordTarget: string | null
12
+ }
13
+
14
+ function CopyButton({ value, label }: { value: string; label: string }) {
15
+ const t = useT()
16
+ const [copied, setCopied] = React.useState(false)
17
+
18
+ const handleCopy = React.useCallback(async () => {
19
+ if (typeof navigator === 'undefined' || !navigator.clipboard) return
20
+ await navigator.clipboard.writeText(value)
21
+ setCopied(true)
22
+ window.setTimeout(() => setCopied(false), 1500)
23
+ }, [value])
24
+
25
+ return (
26
+ <Button type="button" variant="ghost" size="sm" onClick={handleCopy} aria-label={label}>
27
+ {copied ? <Check className="h-4 w-4" aria-hidden /> : <Copy className="h-4 w-4" aria-hidden />}
28
+ <span className="ml-1.5 text-xs">
29
+ {copied
30
+ ? t('customer_accounts.domainMapping.copyTarget.copied', 'Copied')
31
+ : t('customer_accounts.domainMapping.copyTarget', 'Copy')}
32
+ </span>
33
+ </Button>
34
+ )
35
+ }
36
+
37
+ export function DefaultDnsConfig({ hostname, cnameTarget, aRecordTarget }: DnsConfigProps) {
38
+ const t = useT()
39
+ const exampleHost = hostname ?? t('customer_accounts.domainMapping.hostname.placeholder', 'e.g., shop.yourdomain.com')
40
+
41
+ return (
42
+ <div className="space-y-4 rounded-lg border bg-muted/20 p-4">
43
+ <div className="space-y-2">
44
+ <p className="text-sm font-medium text-foreground">
45
+ {t('customer_accounts.domainMapping.cnameInstruction', 'Add a CNAME record for {hostname} pointing to {target} in your DNS provider', {
46
+ hostname: exampleHost,
47
+ target: cnameTarget ?? '—',
48
+ })}
49
+ </p>
50
+ {cnameTarget ? (
51
+ <div className="flex items-center gap-2 rounded-md border bg-background px-3 py-2">
52
+ <code className="flex-1 truncate font-mono text-sm">{cnameTarget}</code>
53
+ <CopyButton value={cnameTarget} label={t('customer_accounts.domainMapping.copyTarget', 'Copy')} />
54
+ </div>
55
+ ) : null}
56
+ </div>
57
+ {aRecordTarget ? (
58
+ <div className="space-y-2">
59
+ <p className="text-sm text-muted-foreground">
60
+ {t('customer_accounts.domainMapping.aRecordInstruction', 'For an apex domain, add an A record for {hostname} pointing to {target}', {
61
+ hostname: exampleHost,
62
+ target: aRecordTarget,
63
+ })}
64
+ </p>
65
+ <div className="flex items-center gap-2 rounded-md border bg-background px-3 py-2">
66
+ <code className="flex-1 truncate font-mono text-sm">{aRecordTarget}</code>
67
+ <CopyButton value={aRecordTarget} label={t('customer_accounts.domainMapping.copyTarget', 'Copy')} />
68
+ </div>
69
+ </div>
70
+ ) : null}
71
+ <p className="text-xs text-muted-foreground">
72
+ {t('customer_accounts.domainMapping.propagationNote', 'DNS changes can take up to 48 hours to propagate. The system checks automatically — you\'ll be notified when it\'s ready.')}
73
+ </p>
74
+ </div>
75
+ )
76
+ }
77
+
78
+ export default DefaultDnsConfig
@@ -0,0 +1,90 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'
5
+ import { Tag } from '@open-mercato/ui/primitives/tag'
6
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
7
+ import { DefaultStepper, type StepperProps } from './Stepper'
8
+ import { DefaultStatusBadge, type StatusBadgeSectionProps } from './StatusBadgeSection'
9
+ import { DefaultDnsConfig, type DnsConfigProps } from './DnsConfig'
10
+ import { DefaultActions, type ActionsProps } from './Actions'
11
+ import { DnsDiagnostics, TlsDiagnostics } from './Diagnostics'
12
+ import type { DomainMappingRow } from './types'
13
+
14
+ export type DomainPanelProps = {
15
+ mapping: DomainMappingRow
16
+ isReplacement: boolean
17
+ cnameTarget: string | null
18
+ aRecordTarget: string | null
19
+ busy?: boolean
20
+ onCheckNow: () => void
21
+ onRetryTls: () => void
22
+ onChangeDomain: () => void
23
+ onRemove: () => void
24
+ }
25
+
26
+ export function DomainPanel(props: DomainPanelProps) {
27
+ const t = useT()
28
+ const Stepper = useRegisteredComponent<StepperProps>(
29
+ 'section:customer_accounts.domain-settings:stepper',
30
+ DefaultStepper,
31
+ )
32
+ const StatusBadgeSection = useRegisteredComponent<StatusBadgeSectionProps>(
33
+ 'section:customer_accounts.domain-settings:status',
34
+ DefaultStatusBadge,
35
+ )
36
+ const DnsConfig = useRegisteredComponent<DnsConfigProps>(
37
+ 'section:customer_accounts.domain-settings:dns-config',
38
+ DefaultDnsConfig,
39
+ )
40
+ const Actions = useRegisteredComponent<ActionsProps>(
41
+ 'section:customer_accounts.domain-settings:actions',
42
+ DefaultActions,
43
+ )
44
+
45
+ const { mapping, isReplacement, cnameTarget, aRecordTarget } = props
46
+ const showDnsConfig = mapping.status !== 'active'
47
+
48
+ return (
49
+ <section className="space-y-4 rounded-xl border bg-card p-6 shadow-sm">
50
+ <div className="flex flex-wrap items-start justify-between gap-3">
51
+ <div className="space-y-1">
52
+ <div className="flex flex-wrap items-center gap-2">
53
+ <h3 className="text-lg font-semibold tracking-tight">{mapping.hostname}</h3>
54
+ {isReplacement ? (
55
+ <Tag variant="info" dot>
56
+ {t('customer_accounts.domainMapping.replacement.label', 'Replacement (waiting to activate)')}
57
+ </Tag>
58
+ ) : null}
59
+ </div>
60
+ <StatusBadgeSection mapping={mapping} />
61
+ </div>
62
+ </div>
63
+
64
+ <Stepper status={mapping.status} />
65
+
66
+ <DnsDiagnostics mapping={mapping} />
67
+ <TlsDiagnostics mapping={mapping} />
68
+
69
+ {showDnsConfig ? (
70
+ <DnsConfig
71
+ hostname={mapping.hostname}
72
+ cnameTarget={mapping.cnameTarget ?? cnameTarget}
73
+ aRecordTarget={mapping.aRecordTarget ?? aRecordTarget}
74
+ />
75
+ ) : null}
76
+
77
+ <Actions
78
+ mapping={mapping}
79
+ isReplacement={isReplacement}
80
+ busy={props.busy}
81
+ onCheckNow={props.onCheckNow}
82
+ onRetryTls={props.onRetryTls}
83
+ onChangeDomain={props.onChangeDomain}
84
+ onRemove={props.onRemove}
85
+ />
86
+ </section>
87
+ )
88
+ }
89
+
90
+ export default DomainPanel
@@ -0,0 +1,43 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
5
+
6
+ export type OrgOption = {
7
+ id: string
8
+ label: string
9
+ depth: number
10
+ }
11
+
12
+ export type OrgSwitcherProps = {
13
+ options: OrgOption[]
14
+ selectedId: string | null
15
+ onChange: (next: string) => void
16
+ }
17
+
18
+ export function OrgSwitcher({ options, selectedId, onChange }: OrgSwitcherProps) {
19
+ const t = useT()
20
+ if (options.length <= 1) return null
21
+ return (
22
+ <label className="flex items-center gap-2 text-sm text-muted-foreground">
23
+ <span>{t('customer_accounts.domainMapping.orgSwitcher.label', 'Organization')}</span>
24
+ <select
25
+ className="h-9 rounded-md border border-input bg-background px-3 text-sm text-foreground shadow-xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40"
26
+ value={selectedId ?? ''}
27
+ onChange={(event) => onChange(event.target.value)}
28
+ >
29
+ {options.map((opt) => {
30
+ const indent = opt.depth > 0 ? '  '.repeat(opt.depth) : ''
31
+ return (
32
+ <option key={opt.id} value={opt.id}>
33
+ {indent}
34
+ {opt.label}
35
+ </option>
36
+ )
37
+ })}
38
+ </select>
39
+ </label>
40
+ )
41
+ }
42
+
43
+ export default OrgSwitcher
@@ -0,0 +1,178 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogFooter,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ } from '@open-mercato/ui/primitives/dialog'
12
+ import { Button } from '@open-mercato/ui/primitives/button'
13
+ import { Input } from '@open-mercato/ui/primitives/input'
14
+ import { Label } from '@open-mercato/ui/primitives/label'
15
+ import { KbdShortcut } from '@open-mercato/ui/primitives/kbd'
16
+ import { Spinner } from '@open-mercato/ui/primitives/spinner'
17
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
18
+
19
+ export type RegisterDialogProps = {
20
+ open: boolean
21
+ onOpenChange: (next: boolean) => void
22
+ mode: 'register' | 'change'
23
+ currentHostname?: string | null
24
+ onSubmit: (hostname: string) => Promise<void>
25
+ initialError?: string | null
26
+ }
27
+
28
+ const HOSTNAME_BASIC = /^[a-z0-9.-]+$/i
29
+
30
+ export function RegisterDialog({
31
+ open,
32
+ onOpenChange,
33
+ mode,
34
+ currentHostname,
35
+ onSubmit,
36
+ initialError,
37
+ }: RegisterDialogProps) {
38
+ const t = useT()
39
+ const [hostname, setHostname] = React.useState('')
40
+ const [error, setError] = React.useState<string | null>(initialError ?? null)
41
+ const [submitting, setSubmitting] = React.useState(false)
42
+ const inputRef = React.useRef<HTMLInputElement>(null)
43
+
44
+ React.useEffect(() => {
45
+ if (!open) {
46
+ setHostname('')
47
+ setError(null)
48
+ setSubmitting(false)
49
+ return
50
+ }
51
+ setError(initialError ?? null)
52
+ window.setTimeout(() => inputRef.current?.focus(), 0)
53
+ }, [open, initialError])
54
+
55
+ const validate = React.useCallback(
56
+ (value: string): string | null => {
57
+ const trimmed = value.trim()
58
+ if (!trimmed) return t('customer_accounts.domainMapping.errors.required', 'Domain is required')
59
+ if (trimmed.length > 253) return t('customer_accounts.domainMapping.hostname.validation.invalid', 'Enter a valid domain name (no protocol, path, or port)')
60
+ if (trimmed.includes('://') || trimmed.includes('/') || trimmed.includes(':')) {
61
+ return t('customer_accounts.domainMapping.hostname.validation.invalid', 'Enter a valid domain name (no protocol, path, or port)')
62
+ }
63
+ if (!HOSTNAME_BASIC.test(trimmed) && !/^xn--/i.test(trimmed)) {
64
+ return t('customer_accounts.domainMapping.hostname.validation.invalid', 'Enter a valid domain name (no protocol, path, or port)')
65
+ }
66
+ return null
67
+ },
68
+ [t],
69
+ )
70
+
71
+ const handleSubmit = React.useCallback(
72
+ async (event: React.FormEvent) => {
73
+ event.preventDefault()
74
+ const validation = validate(hostname)
75
+ if (validation) {
76
+ setError(validation)
77
+ return
78
+ }
79
+ setSubmitting(true)
80
+ setError(null)
81
+ try {
82
+ await onSubmit(hostname.trim())
83
+ } catch (err) {
84
+ const message = err instanceof Error ? err.message : 'Submission failed'
85
+ setError(message)
86
+ } finally {
87
+ setSubmitting(false)
88
+ }
89
+ },
90
+ [hostname, onSubmit, validate],
91
+ )
92
+
93
+ const handleKeyDown = React.useCallback(
94
+ (event: React.KeyboardEvent<HTMLFormElement>) => {
95
+ if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
96
+ event.preventDefault()
97
+ handleSubmit(event)
98
+ }
99
+ },
100
+ [handleSubmit],
101
+ )
102
+
103
+ const title =
104
+ mode === 'change'
105
+ ? t('customer_accounts.domainMapping.dialog.change.title', 'Change custom domain')
106
+ : t('customer_accounts.domainMapping.dialog.register.title', 'Register custom domain')
107
+
108
+ const description =
109
+ mode === 'change' && currentHostname
110
+ ? t(
111
+ 'customer_accounts.domainMapping.changeDomain.description',
112
+ 'Your current domain ({hostname}) will stay active until the new domain is fully set up. No downtime for your customers.',
113
+ { hostname: currentHostname },
114
+ )
115
+ : t('customer_accounts.domainMapping.description', 'Map your own domain to the customer portal')
116
+
117
+ const submitLabel =
118
+ mode === 'change'
119
+ ? t('customer_accounts.domainMapping.dialog.change.submit', 'Start replacement')
120
+ : t('customer_accounts.domainMapping.dialog.register.submit', 'Register')
121
+
122
+ return (
123
+ <Dialog open={open} onOpenChange={onOpenChange}>
124
+ <DialogContent>
125
+ <DialogHeader>
126
+ <DialogTitle>{title}</DialogTitle>
127
+ <DialogDescription>{description}</DialogDescription>
128
+ </DialogHeader>
129
+ <form className="space-y-4" onSubmit={handleSubmit} onKeyDown={handleKeyDown}>
130
+ <div className="space-y-2">
131
+ <Label htmlFor="custom-domain-hostname">
132
+ {t('customer_accounts.domainMapping.hostname.label', 'Domain')}
133
+ </Label>
134
+ <Input
135
+ id="custom-domain-hostname"
136
+ ref={inputRef}
137
+ value={hostname}
138
+ autoComplete="off"
139
+ spellCheck={false}
140
+ placeholder={t('customer_accounts.domainMapping.hostname.placeholder', 'e.g., shop.yourdomain.com')}
141
+ onChange={(event) => {
142
+ setHostname(event.target.value)
143
+ if (error) setError(null)
144
+ }}
145
+ aria-invalid={error ? true : undefined}
146
+ disabled={submitting}
147
+ />
148
+ {error ? (
149
+ <p className="text-sm text-status-error-text" role="alert">
150
+ {error}
151
+ </p>
152
+ ) : null}
153
+ </div>
154
+ <DialogFooter className="items-center">
155
+ <span className="hidden text-xs text-muted-foreground sm:inline-flex sm:items-center sm:gap-1">
156
+ <KbdShortcut keys={['⌘', 'Enter']} />
157
+ </span>
158
+ <Button
159
+ type="button"
160
+ variant="ghost"
161
+ size="default"
162
+ onClick={() => onOpenChange(false)}
163
+ disabled={submitting}
164
+ >
165
+ {t('customer_accounts.domainMapping.dialog.cancel', 'Cancel')}
166
+ </Button>
167
+ <Button type="submit" size="default" disabled={submitting}>
168
+ {submitting ? <Spinner className="mr-2 h-4 w-4" /> : null}
169
+ {submitLabel}
170
+ </Button>
171
+ </DialogFooter>
172
+ </form>
173
+ </DialogContent>
174
+ </Dialog>
175
+ )
176
+ }
177
+
178
+ export default RegisterDialog