Skip to content

Commit be6351c

Browse files
authored
refactor(console): hide tenant convert buttons for dev-only regions (#7750)
refactor(console): hide tenant convert button for dev-only regions
1 parent 5da6792 commit be6351c

File tree

6 files changed

+53
-22
lines changed

6 files changed

+53
-22
lines changed

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

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { type Region as RegionType } from '@logto/cloud/routes';
21
import { Theme, TenantTag } from '@logto/schemas';
32
import { useMemo, useState } from 'react';
43
import { Controller, FormProvider, useForm } from 'react-hook-form';
54
import { toast } from 'react-hot-toast';
65
import { useTranslation } from 'react-i18next';
76
import Modal from 'react-modal';
8-
import useSWRImmutable from 'swr/immutable';
97

108
import CreateTenantHeaderIconDark from '@/assets/icons/create-tenant-header-dark.svg?react';
119
import CreateTenantHeaderIcon from '@/assets/icons/create-tenant-header.svg?react';
@@ -19,6 +17,7 @@ import ModalLayout from '@/ds-components/ModalLayout';
1917
import RadioGroup, { Radio } from '@/ds-components/RadioGroup';
2018
import { Ring } from '@/ds-components/Spinner';
2119
import TextInput from '@/ds-components/TextInput';
20+
import useAvailableRegions from '@/hooks/use-available-regions';
2221
import useTheme from '@/hooks/use-theme';
2322
import modalStyles from '@/scss/modal.module.scss';
2423
import { trySubmitSafe } from '@/utils/form';
@@ -33,19 +32,11 @@ type Props = {
3332
readonly onClose: (tenant?: TenantResponse) => void;
3433
};
3534

36-
const allTags = Object.freeze([TenantTag.Development, TenantTag.Production]);
37-
3835
function CreateTenantModal({ isOpen, onClose }: Props) {
3936
const [tenantData, setTenantData] = useState<CreateTenantData>();
4037
const theme = useTheme();
4138
const cloudApi = useCloudApi();
42-
const { data: regions, error: regionsError } = useSWRImmutable<RegionType[], Error>(
43-
'api/me/regions',
44-
async () => {
45-
const { regions } = await cloudApi.get('/api/me/regions');
46-
return regions;
47-
}
48-
);
39+
const { regions, regionsError, getRegionById } = useAvailableRegions();
4940

5041
const defaultValues = Object.freeze({
5142
tag: TenantTag.Development,
@@ -65,10 +56,7 @@ function CreateTenantModal({ isOpen, onClose }: Props) {
6556
} = methods;
6657

6758
const regionName = watch('regionName');
68-
const currentRegion = useMemo(
69-
() => regions?.find((region) => region.id === regionName),
70-
[regions, regionName]
71-
);
59+
const currentRegion = useMemo(() => getRegionById(regionName), [getRegionById, regionName]);
7260
const createTenant = async ({ name, tag, regionName }: CreateTenantData) => {
7361
const newTenant = await cloudApi.post('/api/tenants', { body: { name, tag, regionName } });
7462
onClose(newTenant);

packages/console/src/consts/tenants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,4 @@ export const availableRegions = Object.freeze([
166166
] as const satisfies PublicRegionName[]);
167167

168168
// The threshold days to show the convert to production card in the get started page
169-
export const convertToProductionThresholdDays = 14;
169+
export const convertToProductionThresholdDays = 7;

packages/console/src/contexts/TenantsProvider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ type Tenants = {
6666
/** The current tenant ID parsed from the URL. */
6767
currentTenantId: string;
6868
currentTenant?: TenantResponse;
69+
/** Indicates if the current tenant is a development tenant. */
6970
isDevTenant: boolean;
7071
/** Navigate to the given tenant ID. */
7172
navigateTenant: (tenantId: string) => void;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { type Region as RegionType } from '@logto/cloud/routes';
2+
import { useCallback } from 'react';
3+
import useSWRImmutable from 'swr/immutable';
4+
5+
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
6+
7+
/** Checks if a region is a development-only region based on its name. */
8+
export const isDevOnlyRegion = (regionName?: string): boolean =>
9+
regionName?.includes('_DEV_') ?? false;
10+
11+
/**
12+
* Hook to fetch available regions for the current user. Cloud API is required to use this hook.
13+
*/
14+
const useAvailableRegions = () => {
15+
const cloudApi = useCloudApi();
16+
const { data: regions, error: regionsError } = useSWRImmutable<RegionType[], Error>(
17+
'api/me/regions',
18+
async () => {
19+
const { regions } = await cloudApi.get('/api/me/regions');
20+
return regions;
21+
}
22+
);
23+
const getRegionById = useCallback(
24+
(id: string) => regions?.find((region) => region.id === id),
25+
[regions]
26+
);
27+
28+
return {
29+
/** Available regions for the current user. */
30+
regions,
31+
/** Error encountered while fetching regions. */
32+
regionsError,
33+
/** Function to get a region by its ID. If the region is not found, returns undefined. */
34+
getRegionById,
35+
};
36+
};
37+
38+
export default useAvailableRegions;

packages/console/src/pages/GetStarted/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { LinkButton } from '@/ds-components/Button';
2323
import Card from '@/ds-components/Card';
2424
import Spacer from '@/ds-components/Spacer';
2525
import TextLink from '@/ds-components/TextLink';
26+
import { isDevOnlyRegion } from '@/hooks/use-available-regions';
2627
import useTenantPathname from '@/hooks/use-tenant-pathname';
2728
import useTheme from '@/hooks/use-theme';
2829
import useWindowResize from '@/hooks/use-window-resize';
@@ -105,7 +106,7 @@ function GetStarted() {
105106
);
106107

107108
const shouldShowConvertToProductionCard = useMemo(() => {
108-
if (!isCloud || !isDevTenant || !currentTenant) {
109+
if (!isCloud || !isDevTenant || !currentTenant || isDevOnlyRegion(currentTenant.regionName)) {
109110
return false;
110111
}
111112

packages/console/src/pages/TenantSettings/TenantBasicSettings/ProfileForm/TenantEnvironment/index.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import { TenantTag } from '@logto/schemas';
2-
import { useState } from 'react';
1+
import { type TenantTag } from '@logto/schemas';
2+
import { useContext, useState } from 'react';
33

44
import RocketIcon from '@/assets/icons/rocket.svg?react';
55
import ConvertToProductionModal from '@/components/ConvertToProductionModal';
66
import LearnMore from '@/components/LearnMore';
77
import TenantEnvTag from '@/components/TenantEnvTag';
88
import { logtoCloudTenantSettings } from '@/consts';
9+
import { TenantsContext } from '@/contexts/TenantsProvider';
910
import Button from '@/ds-components/Button';
1011
import DynamicT from '@/ds-components/DynamicT';
12+
import { isDevOnlyRegion } from '@/hooks/use-available-regions';
1113

1214
import styles from './index.module.scss';
1315

@@ -17,6 +19,7 @@ type Props = {
1719

1820
function TenantEnvironment({ tag }: Props) {
1921
const [isConvertModalOpen, setIsConvertModalOpen] = useState(false);
22+
const { currentTenant, isDevTenant } = useContext(TenantsContext);
2023

2124
return (
2225
<div className={styles.container}>
@@ -25,15 +28,15 @@ function TenantEnvironment({ tag }: Props) {
2528
<div className={styles.description}>
2629
<DynamicT
2730
forKey={
28-
tag === TenantTag.Development
31+
isDevTenant
2932
? 'tenants.settings.development_description'
3033
: 'tenants.settings.production_description'
3134
}
3235
/>
33-
{tag === TenantTag.Development && <LearnMore href={logtoCloudTenantSettings} />}
36+
{isDevTenant && <LearnMore href={logtoCloudTenantSettings} />}
3437
</div>
3538
</div>
36-
{tag === TenantTag.Development && (
39+
{isDevTenant && !isDevOnlyRegion(currentTenant?.regionName) && (
3740
<>
3841
<Button
3942
className={styles.button}

0 commit comments

Comments
 (0)