0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-07 23:01:25 -05:00

feat(console): add tenant deletion guard (#4647)

This commit is contained in:
Xiao Yijun 2023-10-13 14:22:58 +08:00 committed by GitHub
parent 03e654b459
commit e36493367e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 106 additions and 39 deletions

View file

@ -1,10 +1,17 @@
import dayjs from 'dayjs';
import { nanoid } from 'nanoid';
import { useContext } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { toastResponseError, useCloudApi } from '@/cloud/hooks/use-cloud-api';
import { type CreateTenantData } from '@/components/CreateTenantModal/type';
import { checkoutStateQueryKey, checkoutSuccessCallbackPath } from '@/consts/subscriptions';
import {
ReservedPlanId,
checkoutStateQueryKey,
checkoutSuccessCallbackPath,
} from '@/consts/subscriptions';
import { TenantsContext } from '@/contexts/TenantsProvider';
import { createLocalCheckoutSession } from '@/utils/checkout';
import useTenantPathname from './use-tenant-pathname';
@ -18,8 +25,9 @@ type SubscribeProps = {
};
const useSubscribe = () => {
const cloudApi = useCloudApi({ hideErrorToast: true });
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const cloudApi = useCloudApi({ hideErrorToast: true });
const { updateTenant } = useContext(TenantsContext);
const { getUrl } = useTenantPathname();
const subscribe = async ({
@ -70,6 +78,20 @@ const useSubscribe = () => {
tenantId,
},
});
/**
* Note: need to update the tenant's subscription cache data,
* since the cancel subscription flow will not redirect to the stripe payment page.
*/
updateTenant(tenantId, {
planId: ReservedPlanId.free,
subscription: {
status: 'active',
planId: ReservedPlanId.free,
currentPeriodStart: dayjs().toDate(),
currentPeriodEnd: dayjs().add(1, 'month').toDate(),
},
});
};
const visitManagePaymentPage = async (tenantId: string) => {

View file

@ -7,11 +7,13 @@ import { useTranslation } from 'react-i18next';
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
import { type TenantResponse } from '@/cloud/types/router';
import AppError from '@/components/AppError';
import PageMeta from '@/components/PageMeta';
import SubmitFormChangesActionBar from '@/components/SubmitFormChangesActionBar';
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
import { ReservedPlanId } from '@/consts/subscriptions';
import { TenantsContext } from '@/contexts/TenantsProvider';
import { useConfirmModal } from '@/hooks/use-confirm-modal';
import { trySubmitSafe } from '@/utils/form';
import DeleteCard from './DeleteCard';
import DeleteModal from './DeleteModal';
@ -28,12 +30,12 @@ const tenantProfileToForm = (tenant?: TenantResponse): TenantSettingsForm => {
function TenantBasicSettings() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const api = useCloudApi({ hideErrorToast: true });
const api = useCloudApi();
const { currentTenant, currentTenantId, updateTenant, removeTenant, navigateTenant } =
useContext(TenantsContext);
const [error, setError] = useState<Error>();
const [isDeletionModalOpen, setIsDeletionModalOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const { show: showModal } = useConfirmModal();
const methods = useForm<TenantSettingsForm>({
defaultValues: tenantProfileToForm(currentTenant),
@ -50,35 +52,43 @@ function TenantBasicSettings() {
}, [currentTenant, reset]);
const saveData = async (data: { name?: string; tag?: TenantTag }) => {
try {
const { name, tag } = await api.patch(`/api/tenants/:tenantId`, {
params: { tenantId: currentTenantId },
body: data,
});
reset({ profile: { name, tag } });
toast.success(t('tenants.settings.tenant_info_saved'));
updateTenant(currentTenantId, data);
} catch (error: unknown) {
setError(
error instanceof Error
? error
: new Error(JSON.stringify(error, Object.getOwnPropertyNames(error)))
);
}
const { name, tag } = await api.patch(`/api/tenants/:tenantId`, {
params: { tenantId: currentTenantId },
body: data,
});
reset({ profile: { name, tag } });
toast.success(t('tenants.settings.tenant_info_saved'));
updateTenant(currentTenantId, data);
};
const onSubmit = handleSubmit(async (formData: TenantSettingsForm) => {
if (isSubmitting) {
const onSubmit = handleSubmit(
trySubmitSafe(async (formData: TenantSettingsForm) => {
if (isSubmitting) {
return;
}
const {
profile: { name, tag },
} = formData;
await saveData({ name, tag });
})
);
const onClickDeletionButton = async () => {
if (
currentTenant?.subscription.planId !== ReservedPlanId.free ||
currentTenant.openInvoices.length > 0
) {
await showModal({
title: 'tenants.delete_modal.cannot_delete_title',
ModalContent: t('tenants.delete_modal.cannot_delete_description'),
type: 'alert',
cancelButtonText: 'general.got_it',
});
return;
}
const {
profile: { name, tag },
} = formData;
await saveData({ name, tag });
});
const onClickDeletionButton = () => {
setIsDeletionModalOpen(true);
};
@ -93,21 +103,11 @@ function TenantBasicSettings() {
setIsDeletionModalOpen(false);
removeTenant(currentTenantId);
navigateTenant('');
} catch (error: unknown) {
setError(
error instanceof Error
? error
: new Error(JSON.stringify(error, Object.getOwnPropertyNames(error)))
);
} finally {
setIsDeleting(false);
}
};
if (error) {
return <AppError errorMessage={error.message} callStack={error.stack} />;
}
return (
<>
<PageMeta titleKey={['tenants.tabs.settings', 'tenants.title']} />

View file

@ -41,6 +41,9 @@ const tenants = {
description_line3:
'Wenn Sie fortfahren möchten, geben Sie bitte den Mieter-Namen "<span>{{name}}</span>" zur Bestätigung ein.',
delete_button: 'Dauerhaft löschen',
cannot_delete_title: 'Diesen Mandanten kann nicht gelöscht werden',
cannot_delete_description:
'Entschuldigung, Sie können diesen Mandanten momentan nicht löschen. Stellen Sie sicher, dass Sie sich im kostenlosen Tarif befinden und alle ausstehenden Rechnungen bezahlt haben.',
},
tenant_landing_page: {
title: 'Du hast noch keinen Mandanten erstellt',

View file

@ -41,6 +41,9 @@ const tenants = {
description_line3:
'If you would like to proceed, please enter the tenant name "<span>{{name}}</span>" to confirm.',
delete_button: 'Permanently delete',
cannot_delete_title: 'Cannot delete this tenant',
cannot_delete_description:
"Sorry, you can't delete this tenant right now. Please make sure you're on the Free Plan and have paid all outstanding billings.",
},
tenant_landing_page: {
title: "You haven't created a tenant yet",

View file

@ -41,6 +41,9 @@ const tenants = {
description_line3:
'Si desea continuar, ingrese el nombre del inquilino "<span>{{name}}</span>" para confirmar.',
delete_button: 'Eliminar permanentemente',
cannot_delete_title: 'No se puede eliminar este inquilino',
cannot_delete_description:
'Lo siento, no puedes eliminar este inquilino en este momento. Asegúrate de estar en el Plan Gratuito y haber pagado todas las facturas pendientes.',
},
tenant_landing_page: {
title: 'Todavía no has creado un tenant',

View file

@ -41,6 +41,9 @@ const tenants = {
description_line3:
'Si vous souhaitez continuer, veuillez entrer le nom du locataire "<span>{{name}}</span>" pour confirmer.',
delete_button: 'Supprimer définitivement',
cannot_delete_title: 'Impossible de supprimer ce locataire',
cannot_delete_description:
"Désolé, vous ne pouvez pas supprimer ce locataire pour le moment. Assurez-vous d'être sur le Plan Gratuit et d'avoir payé toutes les factures en cours.",
},
tenant_landing_page: {
title: "Vous n'avez pas encore créé de locataire",

View file

@ -42,6 +42,9 @@ const tenants = {
description_line3:
'Se vuoi procedere, inserisci il nome del tenant "<span>{{name}}</span>" per confermare.',
delete_button: 'Elimina definitivamente',
cannot_delete_title: 'Impossibile eliminare questo locatario',
cannot_delete_description:
'Spiacente, al momento non è possibile eliminare questo locatario. Verifica di essere nel Piano Gratuito e di aver saldato tutte le fatture pendenti.',
},
tenant_landing_page: {
title: 'Non hai ancora creato un tenant',

View file

@ -41,6 +41,9 @@ const tenants = {
description_line3:
'続行する場合は、テナント名 "<span>{{name}}</span>" を入力して確認してください。',
delete_button: '完全に削除する',
cannot_delete_title: 'このテナントは削除できません',
cannot_delete_description:
'申し訳ありませんが、現時点ではこのテナントを削除できません。無料プランに登録しており、未払いの請求がないことを確認してください。',
},
tenant_landing_page: {
title: 'まだテナントを作成していません',

View file

@ -41,6 +41,9 @@ const tenants = {
description_line3:
'삭제하려는 테넌트 이름 "<span>{{name}}</span>"을(를) 입력하여 확인하십시오.',
delete_button: '영구 삭제',
cannot_delete_title: '이 테넌트를 삭제할 수 없습니다',
cannot_delete_description:
'죄송합니다. 현재이 테넌트를 삭제할 수 없습니다. 무료 플랜에 있고 미결제 청구서가 없는지 확인하십시오.',
},
tenant_landing_page: {
title: '아직 테넌트를 만들지 않았습니다.',

View file

@ -41,6 +41,9 @@ const tenants = {
description_line3:
'Jeśli chcesz kontynuować, wprowadź nazwę najemcy "<span>{{name}}</span>" w celu potwierdzenia.',
delete_button: 'Usuń na stałe',
cannot_delete_title: 'Nie można usunąć tego najemcy',
cannot_delete_description:
'Przepraszam, nie możesz teraz usunąć tego najemcy. Upewnij się, że korzystasz z planu darmowego i uregulowałeś wszystkie zaległe płatności.',
},
tenant_landing_page: {
title: 'Nie utworzyłeś jeszcze najemcy',

View file

@ -41,6 +41,9 @@ const tenants = {
description_line3:
'Se você deseja continuar, digite o nome do locatário "<span>{{name}}</span>" para confirmar.',
delete_button: 'Excluir permanentemente',
cannot_delete_title: 'Não é possível excluir este inquilino',
cannot_delete_description:
'Desculpe, você não pode excluir este inquilino no momento. Certifique-se de estar no Plano Gratuito e ter pago todas as faturas pendentes.',
},
tenant_landing_page: {
title: 'Você ainda não criou um inquilino',

View file

@ -41,6 +41,9 @@ const tenants = {
description_line3:
'Se desejar continuar, introduza o nome do inquilino "<span>{{name}}</span>" para confirmar.',
delete_button: 'Eliminar permanentemente',
cannot_delete_title: 'Não é possível apagar este inquilino',
cannot_delete_description:
'Desculpe, não é possível apagar este inquilino neste momento. Certifique-se de estar no Plano Gratuito e de ter pago todas as faturas em atraso.',
},
tenant_landing_page: {
title: 'Ainda não criou um inquilino',

View file

@ -41,6 +41,9 @@ const tenants = {
description_line3:
'Если вы хотите продолжить, введите название арендатора "<span>{{name}}</span>" для подтверждения.',
delete_button: 'Навсегда удалить',
cannot_delete_title: 'Нельзя удалить этого арендатора',
cannot_delete_description:
'Извините, вы не можете удалить этого арендатора прямо сейчас. Пожалуйста, убедитесь, что вы используете бесплатный план и оплатили все невыполненные счета.',
},
tenant_landing_page: {
title: 'Вы еще не создали арендатора',

View file

@ -41,6 +41,9 @@ const tenants = {
description_line3:
'Devam etmek isterseniz, "<span>{{name}}</span>" kiracı adını onaylamak için yazın.',
delete_button: 'Kalıcı olarak sil',
cannot_delete_title: 'Bu kiracı silinemez',
cannot_delete_description:
'Üzgünüm, bu kiracıyı şu anda silemezsiniz. Ücretsiz Plan üzerinde olduğunuzdan ve tüm ödenmemiş faturaları ödediğinizden emin olun.',
},
tenant_landing_page: {
title: 'Henüz bir kiracı oluşturmadınız',

View file

@ -38,6 +38,9 @@ const tenants = {
'在删除帐户之前,也许我们可以帮助您。<span><a>通过电子邮件联系我们</a></span>',
description_line3: '如果你想继续,请输入租户名 "<span>{{name}}</span>" 确认。',
delete_button: '永久删除',
cannot_delete_title: '无法删除此租户',
cannot_delete_description:
'抱歉,您现在无法删除此租户。请确保您处于免费计划并已支付所有未结账单。',
},
tenant_landing_page: {
title: '你还没有创建租户',

View file

@ -38,6 +38,9 @@ const tenants = {
'在刪除帳戶之前,也許我們可以為您提供幫助。<span><a>通過電子郵件與我們聯繫</a></span>',
description_line3: '如果您確定要繼續,請輸入租戶名稱 "<span>{{name}}</span>" 以進行確認。',
delete_button: '永久刪除',
cannot_delete_title: '無法刪除此租戶',
cannot_delete_description:
'抱歉,您現在無法刪除此租戶。請確保您處於免費計劃並已支付所有未結賬單。',
},
tenant_landing_page: {
title: '您尚未建立租戶',

View file

@ -38,6 +38,9 @@ const tenants = {
'在刪除帳戶之前,也許我們能提供幫助。<span><a>通過電子郵件與我們聯繫</a></span>',
description_line3: '如果您確定要繼續,請輸入租戶名稱 "<span>{{name}}</span>" 以確認。',
delete_button: '永久刪除',
cannot_delete_title: '無法刪除此租戶',
cannot_delete_description:
'抱歉,您現在無法刪除此租戶。請確保您處於免費計劃並已支付所有未結賬單。',
},
tenant_landing_page: {
title: '您尚未建立租戶',