Skip to content

Commit 04b0766

Browse files
authored
refactor: update @logto/cloud version and update GET /me/regions API use case (#7912)
1 parent b1e7922 commit 04b0766

File tree

10 files changed

+110
-46
lines changed

10 files changed

+110
-46
lines changed

packages/connectors/connector-logto-email/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"access": "public"
5353
},
5454
"devDependencies": {
55-
"@logto/cloud": "0.2.5-c450763",
55+
"@logto/cloud": "0.2.5-af943a1",
5656
"@silverhand/eslint-config": "6.0.1",
5757
"@silverhand/ts-config": "6.0.0",
5858
"@types/node": "^22.14.0",

packages/console/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@fontsource/roboto-mono": "^5.0.0",
2929
"@inkeep/cxkit-react": "^0.5.66",
3030
"@jest/types": "^29.5.0",
31-
"@logto/cloud": "0.2.5-c450763",
31+
"@logto/cloud": "0.2.5-af943a1",
3232
"@logto/connector-kit": "workspace:^",
3333
"@logto/core-kit": "workspace:^",
3434
"@logto/language-kit": "workspace:^",

packages/console/src/components/CreateTenantModal/InstanceSelector/index.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import Dropdown, { DropdownItem } from '@/ds-components/Dropdown';
1010
import styles from './index.module.scss';
1111

1212
type InstanceOption = {
13-
id: string;
1413
name: string;
14+
displayName: string;
1515
country: string;
1616
tags: TenantTag[];
1717
};
@@ -37,7 +37,7 @@ function InstanceSelector({
3737
const buttonRef = useRef<HTMLButtonElement>(null);
3838

3939
const selectedInstance = useMemo(
40-
() => instances.find((instance) => instance.id === value),
40+
() => instances.find((instance) => instance.name === value),
4141
[instances, value]
4242
);
4343

@@ -58,7 +58,7 @@ function InstanceSelector({
5858
>
5959
<div className={styles.selectedContent}>
6060
<RegionFlag regionName={selectedInstance.country} width={20} />
61-
<span className={styles.instanceName}>{selectedInstance.name}</span>
61+
<span className={styles.instanceName}>{selectedInstance.displayName}</span>
6262
</div>
6363
<CaretDown className={styles.icon} />
6464
</button>
@@ -73,9 +73,9 @@ function InstanceSelector({
7373
>
7474
{instances.map((instance) => (
7575
<DropdownItem
76-
key={instance.id}
76+
key={instance.name}
7777
onClick={() => {
78-
onChange(instance.id);
78+
onChange(instance.name);
7979
setIsOpen(false);
8080
if (instance.tags[0]) {
8181
setTenantTagInForm(instance.tags[0]);
@@ -84,11 +84,11 @@ function InstanceSelector({
8484
>
8585
<div className={styles.instanceOption}>
8686
<Tick
87-
className={classNames(styles.checkIcon, instance.id === value && styles.visible)}
87+
className={classNames(styles.checkIcon, instance.name === value && styles.visible)}
8888
/>
8989
<RegionFlag regionName={instance.country} width={20} />
90-
<span className={classNames(instance.id === value && styles.selectedName)}>
91-
{instance.name}
90+
<span className={classNames(instance.name === value && styles.selectedName)}>
91+
{instance.displayName}
9292
</span>
9393
</div>
9494
</DropdownItem>

packages/console/src/components/CreateTenantModal/index.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type Region as RegionType } from '@logto/cloud/routes';
1+
import { type RegionResponse as RegionType } from '@logto/cloud/routes';
22
import { Theme, TenantTag } from '@logto/schemas';
33
import { condArray } from '@silverhand/essentials';
44
import { useCallback, useMemo, useState } from 'react';
@@ -48,7 +48,7 @@ const getInstanceDropdownItems = (regions: RegionType[]): InstanceDropdownItemPr
4848
const hasPublicRegions = regions.some(({ isPrivate }) => !isPrivate);
4949
const privateInstances = regions
5050
.filter(({ isPrivate }) => isPrivate)
51-
.map(({ id, name, country, tags }) => ({ id, name, country, tags }));
51+
.map(({ id, name, country, tags, displayName }) => ({ id, name, country, tags, displayName }));
5252

5353
return condArray(hasPublicRegions && logtoDropdownItem, ...privateInstances);
5454
};
@@ -62,7 +62,7 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
6262

6363
const defaultValues = Object.freeze({
6464
tag: TenantTag.Development,
65-
instanceId: logtoDropdownItem.id,
65+
instanceId: logtoDropdownItem.name,
6666
regionName: defaultRegionName,
6767
});
6868
const methods = useForm<CreateTenantData>({
@@ -95,7 +95,7 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
9595
[regions]
9696
);
9797

98-
const isLogtoInstance = instanceId === logtoDropdownItem.id;
98+
const isLogtoInstance = instanceId === logtoDropdownItem.name;
9999

100100
const currentRegion = useMemo(() => {
101101
if (isDevFeaturesEnabled) {
@@ -106,7 +106,7 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
106106
return getRegionById(regionName);
107107
}
108108
// For private instances, find the region that matches the instance
109-
return regions?.find((region) => region.id === instanceId);
109+
return regions?.find((region) => region.name === instanceId);
110110
}, [isLogtoInstance, regionName, instanceId, getRegionById, regions]);
111111

112112
const getFinalRegionName = useCallback(
@@ -205,13 +205,13 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
205205
<RadioGroup type="plain" name={name} value={value} onChange={onChange}>
206206
{regions.map((region) => (
207207
<Radio
208-
key={region.id}
208+
key={region.name}
209209
title={
210210
<DangerousRaw>
211211
<Region region={region} />
212212
</DangerousRaw>
213213
}
214-
value={region.id}
214+
value={region.name}
215215
isDisabled={isSubmitting}
216216
/>
217217
))}
@@ -264,13 +264,13 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
264264
<RadioGroup type="small" name={name} value={value} onChange={onChange}>
265265
{publicRegions.map((region) => (
266266
<Radio
267-
key={region.id}
267+
key={region.name}
268268
title={
269269
<DangerousRaw>
270270
<Region region={region} />
271271
</DangerousRaw>
272272
}
273-
value={region.id}
273+
value={region.name}
274274
isDisabled={isSubmitting}
275275
/>
276276
))}

packages/console/src/components/Region/index.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type PublicRegionName, type Region as RegionType } from '@logto/cloud/routes';
1+
import { type PublicRegionName, type RegionResponse as RegionType } from '@logto/cloud/routes';
22
import { TenantTag } from '@logto/schemas';
33
import classNames from 'classnames';
44
import { useMemo, type FunctionComponent } from 'react';
@@ -81,11 +81,14 @@ export function StaticRegion({ isComingSoon = false, regionName, className }: St
8181
);
8282
}
8383

84-
export type InstanceDropdownItemProps = Pick<RegionType, 'id' | 'name' | 'country' | 'tags'>;
84+
export type InstanceDropdownItemProps = Pick<
85+
RegionType,
86+
'name' | 'country' | 'tags' | 'displayName'
87+
>;
8588

8689
export const logtoDropdownItem: InstanceDropdownItemProps = {
87-
id: 'logto',
88-
name: 'Logto Cloud (Public)',
90+
name: 'logto',
91+
displayName: 'Logto Cloud (Public)',
8992
country: 'LOGTO',
9093
tags: Object.values(TenantTag),
9194
};
@@ -98,8 +101,8 @@ type Props = {
98101
function Region({ region, className }: Props) {
99102
return (
100103
<span className={classNames(styles.wrapper, className)}>
101-
<RegionFlag regionName={region.id} />
102-
<span>{region.name}</span>
104+
<RegionFlag regionName={region.name} />
105+
<span>{region.displayName}</span>
103106
</span>
104107
);
105108
}

packages/console/src/hooks/use-available-regions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type Region as RegionType } from '@logto/cloud/routes';
1+
import { type RegionResponse as RegionType } from '@logto/cloud/routes';
22
import { useCallback } from 'react';
33
import useSWRImmutable from 'swr/immutable';
44

@@ -21,7 +21,7 @@ const useAvailableRegions = () => {
2121
}
2222
);
2323
const getRegionById = useCallback(
24-
(id: string) => regions?.find((region) => region.id === id),
24+
(id: string) => regions?.find((region) => region.name === id),
2525
[regions]
2626
);
2727

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
"zod": "3.24.3"
102102
},
103103
"devDependencies": {
104-
"@logto/cloud": "0.2.5-c450763",
104+
"@logto/cloud": "0.2.5-af943a1",
105105
"@silverhand/eslint-config": "6.0.1",
106106
"@silverhand/ts-config": "6.0.0",
107107
"@types/adm-zip": "^0.5.5",

packages/core/src/__mocks__/cloud-connection.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@ import { ReservedPlanId } from '@logto/schemas';
33
import { type CloudConnectionLibrary } from '#src/libraries/cloud-connection.js';
44
import { type Subscription } from '#src/utils/subscription/types.js';
55

6+
const defaultSystemLimit: Subscription['systemLimit'] = {
7+
applicationsLimit: 100,
8+
machineToMachineLimit: 20,
9+
thirdPartyApplicationsLimit: 20,
10+
samlApplicationsLimit: 20,
11+
resourcesLimit: 100,
12+
scopesPerResourceLimit: 100,
13+
socialConnectorsLimit: 20,
14+
enterpriseSsoLimit: 100,
15+
userRolesLimit: 1000,
16+
machineToMachineRolesLimit: 100,
17+
organizationsLimit: 100_000,
18+
hooksLimit: 10,
19+
tenantMembersLimit: 100,
20+
};
21+
622
export const mockGetCloudConnectionData: CloudConnectionLibrary['getCloudConnectionData'] =
723
async () => ({
824
resource: 'https://logto.dev',
@@ -49,4 +65,5 @@ export const mockSubscriptionData: Subscription = {
4965
isEnterprisePlan: false,
5066
quota: mockQuota,
5167
status: 'active',
68+
systemLimit: defaultSystemLimit,
5269
};

packages/core/src/utils/subscription/types.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ type RouteResponseType<T extends { search?: unknown; body?: unknown; response?:
1111
type RouteRequestBodyType<T extends { search?: unknown; body?: ZodType; response?: unknown }> =
1212
z.infer<NonNullable<T['body']>>;
1313

14+
type OmitBooleanValueKeys<T> = Omit<
15+
T,
16+
{ [K in keyof T]: T[K] extends boolean ? K : never }[keyof T]
17+
>;
1418
/**
1519
* The subscription data is fetched from the Cloud API.
1620
* All the dates are in ISO 8601 format, we need to manually fix the type to string here.
@@ -110,6 +114,45 @@ const logtoSkuQuotaGuard = z.object({
110114
securityFeaturesEnabled: z.boolean(),
111115
}) satisfies ToZodObject<SubscriptionQuota>;
112116

117+
const systemLimitGuard = (
118+
logtoSkuQuotaGuard
119+
.extend({
120+
usersPerOrganizationLimit: z.number().nullable(),
121+
organizationUserRolesLimit: z.number().nullable(),
122+
organizationMachineToMachineRolesLimit: z.number().nullable(),
123+
organizationScopesLimit: z.number().nullable(),
124+
})
125+
.omit({
126+
/**
127+
* MAU and token limits are not enforced as system limits.
128+
* Usage will be monitored and reported through a separate metering mechanism.
129+
*/
130+
mauLimit: true,
131+
tokenLimit: true,
132+
auditLogsRetentionDays: true,
133+
customJwtEnabled: true,
134+
subjectTokenEnabled: true,
135+
bringYourUiEnabled: true,
136+
collectUserProfileEnabled: true,
137+
mfaEnabled: true,
138+
organizationsEnabled: true,
139+
securityFeaturesEnabled: true,
140+
idpInitiatedSsoEnabled: true,
141+
}) satisfies ToZodObject<
142+
Omit<
143+
OmitBooleanValueKeys<z.infer<typeof logtoSkuQuotaGuard>>,
144+
'mauLimit' | 'tokenLimit' | 'auditLogsRetentionDays'
145+
> & {
146+
/* eslint-disable @typescript-eslint/ban-types */
147+
usersPerOrganizationLimit: number | null;
148+
organizationUserRolesLimit: number | null;
149+
organizationMachineToMachineRolesLimit: number | null;
150+
organizationScopesLimit: number | null;
151+
/* eslint-enable @typescript-eslint/ban-types */
152+
}
153+
>
154+
).partial();
155+
113156
/**
114157
* Redis cache guard for the subscription data returned from the Cloud API `/api/tenants/my/subscription`.
115158
* Logto core does not have access to the zod guard of the subscription data in Cloud,
@@ -125,4 +168,5 @@ export const subscriptionCacheGuard = z.object({
125168
status: subscriptionStatusGuard,
126169
upcomingInvoice: upcomingInvoiceGuard.nullable().optional(),
127170
quota: logtoSkuQuotaGuard,
171+
systemLimit: systemLimitGuard.optional(),
128172
}) satisfies ToZodObject<Subscription>;

0 commit comments

Comments
 (0)