1+ import { type Region as RegionType } from '@logto/cloud/routes' ;
12import { Theme , TenantTag } from '@logto/schemas' ;
3+ import { condArray } from '@silverhand/essentials' ;
24import { useCallback , useMemo , useState } from 'react' ;
35import { Controller , FormProvider , useForm } from 'react-hook-form' ;
46import { toast } from 'react-hot-toast' ;
@@ -11,8 +13,8 @@ import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
1113import { type TenantResponse } from '@/cloud/types/router' ;
1214import Region , {
1315 defaultRegionName ,
14- getInstanceDropdownItems ,
1516 logtoDropdownItem ,
17+ type InstanceDropdownItemProps ,
1618} from '@/components/Region' ;
1719import { isDevFeaturesEnabled } from '@/consts/env' ;
1820import Button from '@/ds-components/Button' ;
@@ -38,6 +40,19 @@ type Props = {
3840 readonly onClose : ( tenant ?: TenantResponse ) => void ;
3941} ;
4042
43+ const checkPrivateRegionAccess = ( regions : RegionType [ ] ) : boolean => {
44+ return regions . some ( ( { isPrivate } ) => isPrivate ) ;
45+ } ;
46+
47+ const getInstanceDropdownItems = ( regions : RegionType [ ] ) : InstanceDropdownItemProps [ ] => {
48+ const hasPublicRegions = regions . some ( ( { isPrivate } ) => ! isPrivate ) ;
49+ const privateInstances = regions
50+ . filter ( ( { isPrivate } ) => isPrivate )
51+ . map ( ( { id, name, country, tags } ) => ( { id, name, country, tags } ) ) ;
52+
53+ return condArray ( hasPublicRegions && logtoDropdownItem , ...privateInstances ) ;
54+ } ;
55+
4156// eslint-disable-next-line complexity
4257function CreateTenantModal ( { isOpen, onClose } : Props ) {
4358 const [ tenantData , setTenantData ] = useState < CreateTenantData > ( ) ;
@@ -73,6 +88,7 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
7388 ) ;
7489
7590 const instanceOptions = useMemo ( ( ) => getInstanceDropdownItems ( regions ?? [ ] ) , [ regions ] ) ;
91+ const hasPrivateRegionsAccess = useMemo ( ( ) => checkPrivateRegionAccess ( regions ?? [ ] ) , [ regions ] ) ;
7692
7793 const publicRegions = useMemo (
7894 ( ) => regions ?. filter ( ( region ) => ! region . isPrivate ) ?? [ ] ,
@@ -83,7 +99,7 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
8399
84100 const currentRegion = useMemo ( ( ) => {
85101 if ( isDevFeaturesEnabled ) {
86- return getRegionById ( regionName ) ;
102+ return getRegionById ( isLogtoInstance ? regionName : instanceId ) ;
87103 }
88104
89105 if ( isLogtoInstance ) {
@@ -93,14 +109,20 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
93109 return regions ?. find ( ( region ) => region . id === instanceId ) ;
94110 } , [ isLogtoInstance , regionName , instanceId , getRegionById , regions ] ) ;
95111
112+ const getFinalRegionName = useCallback (
113+ ( instanceId : string , regionName : string ) => {
114+ if ( isDevFeaturesEnabled ) {
115+ return isLogtoInstance ? regionName : instanceId ;
116+ }
117+ return regionName ;
118+ } ,
119+ [ isLogtoInstance ]
120+ ) ;
121+
96122 const createTenant = async ( { name, tag, instanceId, regionName } : CreateTenantData ) => {
97123 // For Logto public instance, use the selected region
98124 // For private instances, use the instance ID as the region
99- const finalRegionName = isDevFeaturesEnabled
100- ? isLogtoInstance
101- ? regionName
102- : instanceId
103- : regionName ;
125+ const finalRegionName = getFinalRegionName ( instanceId , regionName ) ;
104126 const newTenant = await cloudApi . post ( '/api/tenants' , {
105127 body : { name, tag, regionName : finalRegionName } ,
106128 } ) ;
@@ -117,7 +139,8 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
117139 return ;
118140 }
119141
120- setTenantData ( data ) ;
142+ // For production tenants, store creation parameters with the correct regionName for later use after plan selection.
143+ setTenantData ( { ...data , regionName : getFinalRegionName ( data . instanceId , data . regionName ) } ) ;
121144 } )
122145 ) ;
123146
@@ -199,32 +222,8 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
199222 </ FormField >
200223 ) }
201224
202- { currentRegion && (
203- < FormField title = "tenants.create_modal.tenant_usage_purpose" >
204- < Controller
205- control = { control }
206- name = "tag"
207- rules = { { required : true } }
208- render = { ( { field : { onChange, value, name } } ) => (
209- < RadioGroup
210- type = "card"
211- className = { styles . envTagRadioGroup }
212- value = { value }
213- name = { name }
214- onChange = { onChange }
215- >
216- { currentRegion . tags . map ( ( tag ) => (
217- < Radio key = { tag } value = { tag } >
218- < EnvTagOptionContent tag = { tag } />
219- </ Radio >
220- ) ) }
221- </ RadioGroup >
222- ) }
223- />
224- </ FormField >
225- ) }
226-
227- { isDevFeaturesEnabled && (
225+ { /* Only show the instance selector (dropdown) if there are private regions available. */ }
226+ { isDevFeaturesEnabled && hasPrivateRegionsAccess && (
228227 < FormField
229228 title = "tenants.settings.tenant_instance"
230229 tip = { t ( 'tenants.settings.tenant_instance_description' ) }
@@ -281,6 +280,35 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
281280 ) }
282281 </ FormField >
283282 ) }
283+
284+ { currentRegion && (
285+ < FormField title = "tenants.create_modal.tenant_usage_purpose" >
286+ < Controller
287+ control = { control }
288+ name = "tag"
289+ rules = { { required : true } }
290+ render = { ( { field : { onChange, value, name } } ) => (
291+ < RadioGroup
292+ type = "card"
293+ className = { styles . envTagRadioGroup }
294+ value = { value }
295+ name = { name }
296+ onChange = { onChange }
297+ >
298+ { currentRegion . tags . map ( ( tag ) => (
299+ < Radio key = { tag } value = { tag } >
300+ { /* If the region is private (for enterprise customers), we hide the available production plan. */ }
301+ < EnvTagOptionContent
302+ tag = { tag }
303+ isAvailableProductionPlanInvisible = { currentRegion . isPrivate }
304+ />
305+ </ Radio >
306+ ) ) }
307+ </ RadioGroup >
308+ ) }
309+ />
310+ </ FormField >
311+ ) }
284312 </ FormProvider >
285313 < SelectTenantPlanModal
286314 tenantData = { tenantData }
0 commit comments