Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"@iconscout/react-unicons": "^1.1.6",
"@internxt/css-config": "1.1.0",
"@internxt/lib": "1.4.1",
"@internxt/sdk": "=1.15.6",
"@internxt/sdk": "=1.15.13",
"@internxt/ui": "=0.1.15",
"@phosphor-icons/react": "^2.1.7",
"@popperjs/core": "^2.11.6",
Expand Down
3 changes: 3 additions & 0 deletions src/app/banners/BannerManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ describe('BannerManager - showFreeBanner', () => {
storageLimit: 0,
amountOfSeats: 1,
seats: { minimumSeats: 1, maximumSeats: 1 },
commitment: { enabled: false },
},
businessPlan: null,
planLimit: 0,
Expand Down Expand Up @@ -181,6 +182,7 @@ describe('BannerManager - showSubscriptionBanner', () => {
storageLimit: 0,
amountOfSeats: 1,
seats: { minimumSeats: 1, maximumSeats: 1 },
commitment: { enabled: false },
},
businessPlan: null,
planLimit: 0,
Expand Down Expand Up @@ -220,6 +222,7 @@ describe('BannerManager - showSubscriptionBanner', () => {
storageLimit: 0,
amountOfSeats: 1,
seats: { minimumSeats: 1, maximumSeats: 1 },
commitment: { enabled: false },
},
businessPlan: null,
planLimit: 0,
Expand Down
8 changes: 8 additions & 0 deletions src/app/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,13 @@
"individual": "Sie sind dabei, Ihren Internxt-Tarif {{currentPlanName}} zu kündigen. Durch die Kündigung Ihres Tarifs werden Sie auf den kostenlosen Internxt-Tarif {{freePlanName}} herabgestuft. Bitte bestätigen Sie Ihre Wahl, um fortzufahren.",
"business": "Durch die Kündigung Ihres Tarifs wird der Workspace zusammen mit allen Daten gelöscht. Bitte bestätigen Sie Ihre Wahl, um fortzufahren."
},
"commitment": {
"firstMonthDescription": "Da Sie sich noch in Ihrer 30-tägigen Testphase befinden, wird Ihre Kündigung sofort wirksam. Sie werden nicht mehr belastet und Ihr Konto wird jetzt auf den kostenlosen 1-GB-Plan zurückgesetzt.",
"description": "Sie haben ein Jahresabonnement, das monatlich abgerechnet wird. Wenn Sie jetzt kündigen, bleibt Ihr Abonnement bis zum {{end_date}} aktiv, und Sie werden bis zu diesem Datum weiterhin monatlich belastet.",
"monthsRemaining": "Verbleibende Zahlungen: {{monthsRemaining}}",
"amountPerMonth": "Betrag pro Monat: €{{amount}}",
"afterLabel": "Nach dem {{monthsRemaining}}"
},
"keepSubscription": "Abonnement behalten",
"continue": "Weiter",
"cancelSubscription": "Abonnement beenden",
Expand Down Expand Up @@ -1963,6 +1970,7 @@
"manageBilling": "Abrechnung verwalten",
"freeForever": "Für immer kostenlos",
"billedAnnually": "jährlich abgerechnet",
"billedMonthly": "monatlich abgerechnet",
"year": "Jahr",
"month": "Monat",
"lifetime": "Lebenslang",
Expand Down
8 changes: 8 additions & 0 deletions src/app/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,13 @@
"individual": "You are about to cancel your {{currentPlanName}} Internxt plan. By cancelling your plan, you will be downgraded to free Internxt {{freePlanName}} plan. Confirm your choice to proceed, please.",
"business": "By cancelling your plan, the workspace will be deleted along with all data. Confirm your choice to proceed, please."
},
"commitment": {
"firstMonthDescription": "As you're still in your 30 day trial period, your cancellation will be immediate. You will no longer be billed and your account will revert to a free 1GB plan now.",
"description": "You have an annual subscription billed monthly. If you cancel now, your subscription will remain active until {{end_date}}, and you will continue to be charged monthly until that date.",
"monthsRemaining": "Remaining payments: {{monthsRemaining}}",
"amountPerMonth": "Amount per month: €{{amount}}",
"afterLabel": "After {{monthsRemaining}}"
},
"keepSubscription": "Keep subscription",
"continue": "Continue",
"cancelSubscription": "Cancel subscription",
Expand Down Expand Up @@ -2047,6 +2054,7 @@
"manageBilling": "Manage billing",
"freeForever": "Free forever",
"billedAnnually": "billed annually",
"billedMonthly": "billed monthly",
"year": "Year",
"month": "Month",
"lifetime": "Lifetime",
Expand Down
8 changes: 8 additions & 0 deletions src/app/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,13 @@
"individual": "Estás a punto de cancelar tu plan de Internxt {{currentPlanName}}. Al cancelar tu plan, pasarás al plan gratuito de Internxt {{freePlanName}}. Por favor, confirma tu elección para continuar.",
"business": "Al cancelar tu plan, el espacio de trabajo se eliminará junto con todos los datos. Por favor, confirma tu elección para continuar."
},
"commitment": {
"firstMonthDescription": "Como todavía estás en tu período de prueba de 30 días, tu cancelación será inmediata. Ya no se te cobrará y tu cuenta volverá al plan gratuito de 1 GB ahora.",
"description": "Tienes una suscripción anual facturada mensualmente. Si cancelas ahora, tu suscripción permanecerá activa hasta el {{end_date}}, y seguirás siendo cobrado mensualmente hasta esa fecha.",
"monthsRemaining": "Pagos restantes: {{monthsRemaining}}",
"amountPerMonth": "Importe por mes: €{{amount}}",
"afterLabel": "Después del {{monthsRemaining}}"
},
"keepSubscription": "Mantener suscripción",
"continue": "Continuar",
"cancelSubscription": "Cancelar suscripción",
Expand Down Expand Up @@ -2026,6 +2033,7 @@
"month": "Mes",
"lifetime": "Lifetime",
"billedAnnually": "facturado anualmente",
"billedMonthly": "facturado mensualmente",
"types": {
"essential": "Essential",
"premium": "Premium",
Expand Down
8 changes: 8 additions & 0 deletions src/app/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,13 @@
"individual": "Vous êtes sur le point d’annuler votre abonnement Internxt {{currentPlanName}}. En annulant votre abonnement, vous serez rétrogradé(e) à l’abonnement gratuit Internxt {{freePlanName}}. Veuillez confirmer votre choix pour continuer.",
"business": "En annulant votre abonnement, l’espace de travail sera supprimé ainsi que toutes les données. Veuillez confirmer votre choix pour continuer."
},
"commitment": {
"firstMonthDescription": "Comme vous êtes encore dans votre période d'essai de 30 jours, votre annulation sera immédiate. Vous ne serez plus facturé et votre compte reviendra au plan gratuit de 1 Go maintenant.",
"description": "Vous avez un abonnement annuel facturé mensuellement. Si vous annulez maintenant, votre abonnement restera actif jusqu’au {{end_date}}, et vous continuerez à être facturé mensuellement jusqu’à cette date.",
"monthsRemaining": "Paiements restants : {{monthsRemaining}}",
"amountPerMonth": "Montant par mois : €{{amount}}",
"afterLabel": "Après le {{monthsRemaining}}"
},
"keepSubscription": "Conserver l’abonnement",
"continue": "Continuer",
"cancelSubscription": "Annuler l’abonnement",
Expand Down Expand Up @@ -1969,6 +1976,7 @@
"manageBilling": "Gérer la facturation",
"freeForever": "Gratuit à vie",
"billedAnnually": "facturé annuellement",
"billedMonthly": "facturé mensuellement",
"year": "Année",
"month": "Mois",
"lifetime": "À vie",
Expand Down
8 changes: 8 additions & 0 deletions src/app/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,13 @@
"individual": "Stai per annullare il tuo piano Internxt {{currentPlanName}}. Annullando il tuo piano, verrai retrocesso al piano gratuito di Internxt {{freePlanName}}. Conferma la tua scelta per procedere, per favore.",
"business": "Annullando il tuo piano, lo spazio di lavoro verrà eliminato insieme a tutti i dati. Per favore, conferma la tua scelta per procedere."
},
"commitment": {
"firstMonthDescription": "Poiché sei ancora nel tuo periodo di prova di 30 giorni, la tua cancellazione sarà immediata. Non ti verrà più addebitato nulla e il tuo account tornerà ora al piano gratuito da 1 GB.",
"description": "Hai un abbonamento annuale fatturato mensilmente. Se annulli ora, il tuo abbonamento rimarrà attivo fino al {{end_date}} e continuerai a essere addebitato mensilmente fino a quella data.",
"monthsRemaining": "Pagamenti rimanenti: {{monthsRemaining}}",
"amountPerMonth": "Importo al mese: €{{amount}}",
"afterLabel": "Dopo il {{monthsRemaining}}"
},
"keepSubscription": "Mantieni l'abbonamento",
"continua": "Continua",
"cancelSubscription": "Annulla abbonamento",
Expand Down Expand Up @@ -2076,6 +2083,7 @@
"manageBilling": "Gestisci la fatturazione",
"freeForever": "Gratis per sempre",
"billedAnnually": "fatturato annualmente",
"billedMonthly": "fatturato mensilmente",
"year": "Anno",
"month": "Mese",
"lifetime": "A vita",
Expand Down
8 changes: 8 additions & 0 deletions src/app/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,13 @@
"individual": "Вы собираетесь отменить свой план Internxt {{currentPlanName}}. Отменив этот план, вы будете переведены на бесплатный план Internxt {{freePlanName}}. Пожалуйста, подтвердите свой выбор, чтобы продолжить.",
"business": "Отменив свой план, рабочая область будет удалена вместе со всеми данными. Пожалуйста, подтвердите свой выбор, чтобы продолжить."
},
"commitment": {
"firstMonthDescription": "Поскольку вы всё ещё находитесь в 30-дневном пробном периоде, отмена будет немедленной. С вас больше не будут взиматься платежи, и ваш аккаунт сейчас вернётся к бесплатному плану на 1 ГБ.",
"description": "У вас есть годовая подписка с ежемесячной оплатой. Если вы отмените сейчас, ваша подписка останется активной до {{end_date}}, и с вас будет ежемесячно взиматься плата до этой даты.",
"monthsRemaining": "Оставшихся платежей: {{monthsRemaining}}",
"amountPerMonth": "Сумма в месяц: €{{amount}}",
"afterLabel": "После {{monthsRemaining}}"
},
"keepSubscription": "Оставить подписку",
"continue": "Продолжить",
"cancelSubscription": "Отменить подписку",
Expand Down Expand Up @@ -1984,6 +1991,7 @@
"manageBilling": "Управление платежами",
"freeForever": "Бесплатно навсегда",
"billedAnnually": "оплачиваемый ежегодно",
"billedMonthly": "оплачиваемый ежемесячно",
"year": "Год",
"month": "Месяц",
"lifetime": "Пожизненно",
Expand Down
8 changes: 8 additions & 0 deletions src/app/i18n/locales/tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,13 @@
"individual": "您即將取消您的 Internxt {{currentPlanName}} 方案。取消方案後,您將降級為免費的 Internxt {{freePlanName}} 方案。請確認您的選擇以繼續。",
"business": "取消方案後,工作區以及所有資料將被刪除。請確認您的選擇以繼續。"
},
"commitment": {
"firstMonthDescription": "由於您仍在 30 天試用期內,您的取消將立即生效。您將不再被收費,您的帳戶現在將恢復為免費的 1GB 方案。",
"description": "您擁有按月計費的年度訂閱。如果您現在取消,您的訂閱將保持有效至 {{end_date}},且在該日期之前您將繼續被按月收費。",
"monthsRemaining": "剩餘付款次數:{{monthsRemaining}}",
"amountPerMonth": "每月金額:€{{amount}}",
"afterLabel": "{{monthsRemaining}} 之後"
},
"keepSubscription": "保留訂閱",
"continue": "繼續",
"cancelSubscription": "取消訂閱",
Expand Down Expand Up @@ -1973,6 +1980,7 @@
"manageBilling": "管理帳單",
"freeForever": "永久免費",
"billedAnnually": "按年計費",
"billedMonthly": "按月計費",
"year": "年",
"month": "月",
"lifetime": "終身",
Expand Down
8 changes: 8 additions & 0 deletions src/app/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,13 @@
"individual": "您即将取消您的 Internxt {{currentPlanName}} 计划。取消计划后,您将降级为免费的 Internxt {{freePlanName}} 计划。请确认您的选择以继续。",
"business": "取消计划后,工作区以及所有数据将被删除。请确认您的选择以继续。"
},
"commitment": {
"firstMonthDescription": "由于您仍在 30 天试用期内,您的取消将立即生效。您将不再被收费,您的账户现在将恢复为免费的 1GB 计划。",
"description": "您拥有按月计费的年度订阅。如果您现在取消,您的订阅将保持有效至 {{end_date}},且在该日期之前您将继续被按月收费。",
"monthsRemaining": "剩余付款次数:{{monthsRemaining}}",
"amountPerMonth": "每月金额:€{{amount}}",
"afterLabel": "{{monthsRemaining}} 之后"
},
"keepSubscription": "继续订阅",
"continue": "继续",
"cancelSubscription": "取消订阅",
Expand Down Expand Up @@ -2011,6 +2018,7 @@
"manageBilling": "管理账单",
"freeForever": "永久免费",
"billedAnnually": "按年计费",
"billedMonthly": "按月计费",
"year": "年",
"month": "月",
"lifetime": "终身",
Expand Down
2 changes: 2 additions & 0 deletions src/app/store/slices/plan/plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const mockIndividualPlan: StoragePlan = {
renewalPeriod: RenewalPeriod.Monthly,
storageLimit: 1000000000,
amountOfSeats: 1,
commitment: { enabled: false },
};

const mockBusinessPlan: StoragePlan = {
Expand All @@ -33,6 +34,7 @@ const mockBusinessPlan: StoragePlan = {
renewalPeriod: RenewalPeriod.Monthly,
storageLimit: 5000000000,
amountOfSeats: 5,
commitment: { enabled: false },
};

const createMockState = (planState: Partial<PlanState>, hasSelectedWorkspace = false): Partial<RootState> => ({
Expand Down
40 changes: 39 additions & 1 deletion src/services/error.service.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { AxiosError, AxiosResponse } from 'axios';
import { AppError } from '@internxt/sdk';
import { AxiosResponseError } from '@internxt/sdk/dist/shared/types/errors';
import errorService from './error.service';
import envService from './env.service';

Expand All @@ -20,6 +21,17 @@ describe('Error Service', () => {
vi.restoreAllMocks();
});

const createAxiosResponseError = (data: unknown, status: number, xRequestId?: string): AxiosResponseError => {
const response = {
data,
status,
statusText: '',
headers: { 'x-request-id': xRequestId },
config: {} as never,
} as AxiosResponse;
return new AxiosResponseError('Request failed', 'POST /auth/login', response);
};

const createAxiosError = (data: unknown, status?: number, headers?: Record<string, string>): AxiosError => {
const error = new AxiosError('Request failed');
if (status !== undefined) {
Expand All @@ -38,7 +50,33 @@ describe('Error Service', () => {
});
});

describe('castError', () => {
describe('Cast Error', () => {
describe('AxiosResponseError handling', () => {
it('when response body has a message field, then uses it as the error message', () => {
const result = errorService.castError(createAxiosResponseError({ message: 'Wrong login credentials' }, 400));
expect(result.message).toBe('Wrong login credentials');
expect(result.status).toBe(400);
});

it('when response body has only an error field, then uses it as the error message', () => {
const result = errorService.castError(createAxiosResponseError({ error: 'Wrong login credentials' }, 400));
expect(result.message).toBe('Wrong login credentials');
expect(result.status).toBe(400);
});

it('when response body has no message or error fields, then falls back to the axios error message', () => {
const result = errorService.castError(createAxiosResponseError({}, 400));
expect(result.message).toBe('Request failed');
expect(result.status).toBe(400);
});

it('when response includes the id request in the header, then it is extracted correctly', () => {
const result = errorService.castError(createAxiosResponseError({ message: 'Fail' }, 500, 'req-789'));
expect(result.requestId).toBe('req-789');
expect(result.status).toBe(500);
});
});

describe('AxiosError handling', () => {
it('uses the error field from API responses when both error and message are present', () => {
const result1 = errorService.castError(createAxiosError({ error: 'Custom error message' }, 400));
Expand Down
8 changes: 8 additions & 0 deletions src/services/error.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AxiosError } from 'axios';
import { AppError } from '@internxt/sdk';
import { AxiosResponseError } from '@internxt/sdk/dist/shared/types/errors';
import envService from './env.service';

interface AxiosErrorResponse {
Expand Down Expand Up @@ -32,6 +33,13 @@ const errorService = {
return err;
}

if (err instanceof AxiosResponseError) {
const data = err.data as AxiosErrorResponse | undefined;
const message = data?.message || data?.error || err.message || 'Unknown error';
castedError = new AppError(message, err.status, undefined, { 'x-request-id': err.xRequestId ?? '' });
return castedError;
}

if (err instanceof AxiosError) {
const axiosError = err as AxiosError<AxiosErrorResponse>;
const responseData = axiosError.response?.data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const BillingAccountSection = ({ changeSection, onClosePreferences }: BillingAcc
<Invoices userType={UserType.Individual} />
{isSubscription && (
<CancelSubscription
individualPlan={plan.individualPlan}
isCancelSubscriptionModalOpen={isCancelSubscriptionModalOpen}
setIsCancelSubscriptionModalOpen={setIsCancelSubscriptionModalOpen}
cancellingSubscription={cancellingSubscription}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { t } from 'i18next';
import { Button } from '@internxt/ui';
import CancelSubscriptionModal from '../../../Workspace/Billing/CancelSubscriptionModal';
import { UserType } from '@internxt/sdk/dist/drive/payments/types/types';
import { StoragePlan, UserType } from '@internxt/sdk/dist/drive/payments/types/types';

interface CancelSubscriptionProps {
individualPlan: StoragePlan | null;
isCancelSubscriptionModalOpen: boolean;
setIsCancelSubscriptionModalOpen: (isCancelSubscriptionModalOpen: boolean) => void;
cancellingSubscription: boolean;
Expand All @@ -15,6 +16,7 @@ interface CancelSubscriptionProps {
}

const CancelSubscription = ({
individualPlan,
isCancelSubscriptionModalOpen,
setIsCancelSubscriptionModalOpen,
cancellingSubscription,
Expand All @@ -39,6 +41,7 @@ const CancelSubscription = ({
{t('preferences.workspace.billing.cancelSubscription.button')}
</Button>
<CancelSubscriptionModal
individualPlan={individualPlan}
isOpen={isCancelSubscriptionModalOpen}
onClose={() => {
setIsCancelSubscriptionModalOpen(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ const PlansSection = ({ changeSection, onClosePreferences }: PlansSectionProps)

<CancelSubscriptionModal
isOpen={isCancelSubscriptionModalOpen}
individualPlan={plan.individualPlan}
onClose={() => {
setIsCancelSubscriptionModalOpen(false);
}}
Expand Down
Loading
Loading