Remove team account related services and actions

Removed services and actions related to team account deletion as well as updated paths within other dependent files, better reflecting their new locations. Also, added a new service titled 'AccountBillingService' for handling billing-related operations and restructured the form layout and handled translation in 'team-account-danger-zone' component.
This commit is contained in:
giancarlo
2024-03-28 15:27:56 +08:00
parent 3ac4d3b00d
commit 041efb89fb
77 changed files with 1998 additions and 1553 deletions

View File

@@ -17,6 +17,7 @@ import {
} from '@kit/ui/command';
import { If } from '@kit/ui/if';
import { Popover, PopoverContent, PopoverTrigger } from '@kit/ui/popover';
import { Trans } from '@kit/ui/trans';
import { cn } from '@kit/ui/utils';
import { CreateTeamAccountDialog } from '../../../team-accounts/src/components/create-team-account-dialog';
@@ -29,8 +30,8 @@ interface AccountSelectorProps {
}>;
features: {
enableOrganizationAccounts: boolean;
enableOrganizationCreation: boolean;
enableTeamAccounts: boolean;
enableTeamCreation: boolean;
};
selectedAccount?: string;
@@ -46,8 +47,8 @@ export function AccountSelector({
selectedAccount,
onAccountChange,
features = {
enableOrganizationAccounts: true,
enableOrganizationCreation: true,
enableTeamAccounts: true,
enableTeamCreation: true,
},
collapsed = false,
}: React.PropsWithChildren<AccountSelectorProps>) {
@@ -75,6 +76,10 @@ export function AccountSelector({
const selected = accounts.find((account) => account.value === value);
if (!features.enableTeamAccounts) {
return null;
}
return (
<>
<Popover open={open} onOpenChange={setOpen}>
@@ -150,9 +155,9 @@ export function AccountSelector({
<CommandSeparator />
<If condition={features.enableOrganizationAccounts}>
<If condition={features.enableTeamAccounts}>
<If condition={accounts.length > 0}>
<CommandGroup heading={'Your Organizations'}>
<CommandGroup heading={<Trans i18nKey={'teams:yourTeams'} />}>
{(accounts ?? []).map((account) => (
<CommandItem
key={account.value}
@@ -185,7 +190,7 @@ export function AccountSelector({
</If>
</If>
<If condition={features.enableOrganizationCreation}>
<If condition={features.enableTeamCreation}>
<CommandGroup>
<Button
size={'sm'}
@@ -198,7 +203,9 @@ export function AccountSelector({
>
<Plus className="mr-2 h-4 w-4" />
<span>Create Organization</span>
<span>
<Trans i18nKey={'teams:createTeam'} />
</span>
</Button>
</CommandGroup>
</If>
@@ -207,7 +214,7 @@ export function AccountSelector({
</PopoverContent>
</Popover>
<If condition={features.enableOrganizationCreation}>
<If condition={features.enableTeamCreation}>
<CreateTeamAccountDialog
isOpen={isCreatingAccount}
setIsOpen={setIsCreatingAccount}

View File

@@ -18,6 +18,7 @@ import {
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@kit/ui/alert-dialog';
@@ -166,18 +167,20 @@ function ConfirmUnenrollFactorModal(
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogAction
className={'w-full'}
type={'button'}
disabled={unEnroll.isPending}
onClick={() => onUnenrollRequested(props.factorId)}
>
<Trans i18nKey={'account:unenrollFactorModalButtonLabel'} />
</AlertDialogAction>
<AlertDialogFooter>
<AlertDialogCancel>
<Trans i18nKey={'common:cancel'} />
</AlertDialogCancel>
<AlertDialogCancel>
<Trans i18nKey={'common:cancel'} />
</AlertDialogCancel>
<AlertDialogAction
className={'w-full'}
type={'button'}
disabled={unEnroll.isPending}
onClick={() => onUnenrollRequested(props.factorId)}
>
<Trans i18nKey={'account:unenrollFactorModalButtonLabel'} />
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);

View File

@@ -212,20 +212,22 @@ function MultiFactorAuthSetupForm({
name={'verificationCode'}
/>
<Button
disabled={!verificationCodeForm.formState.isValid}
type={'submit'}
>
{state.loading ? (
<Trans i18nKey={'account:verifyingCode'} />
) : (
<Trans i18nKey={'account:enableMfaFactor'} />
)}
</Button>
<div className={'flex space-x-2'}>
<Button type={'button'} variant={'ghost'} onClick={onCancel}>
<Trans i18nKey={'common:cancel'} />
</Button>
<Button type={'button'} variant={'ghost'} onClick={onCancel}>
<Trans i18nKey={'common:cancel'} />
</Button>
<Button
disabled={!verificationCodeForm.formState.isValid}
type={'submit'}
>
{state.loading ? (
<Trans i18nKey={'account:verifyingCode'} />
) : (
<Trans i18nKey={'account:enableMfaFactor'} />
)}
</Button>
</div>
</div>
</form>
</Form>
@@ -362,13 +364,15 @@ function FactorNameForm(
}}
/>
<Button type={'submit'}>
<Trans i18nKey={'account:factorNameSubmitLabel'} />
</Button>
<div className={'flex space-x-2'}>
<Button type={'button'} variant={'ghost'} onClick={props.onCancel}>
<Trans i18nKey={'common:cancel'} />
</Button>
<Button type={'button'} variant={'ghost'} onClick={props.onCancel}>
<Trans i18nKey={'common:cancel'} />
</Button>
<Button type={'submit'}>
<Trans i18nKey={'account:factorNameSubmitLabel'} />
</Button>
</div>
</div>
</form>
</Form>

View File

@@ -1,5 +1,7 @@
'use client';
import { LoadingOverlay } from '@kit/ui/loading-overlay';
import {
usePersonalAccountData,
useRevalidatePersonalAccountDataQuery,
@@ -8,7 +10,11 @@ import { UpdateAccountDetailsForm } from './update-account-details-form';
export function UpdateAccountDetailsFormContainer() {
const user = usePersonalAccountData();
const invalidateUserDataQuery = useRevalidatePersonalAccountDataQuery();
const revalidateUserDataQuery = useRevalidatePersonalAccountDataQuery();
if (user.isLoading) {
return <LoadingOverlay fullPage={false} />;
}
if (!user.data) {
return null;
@@ -18,7 +24,7 @@ export function UpdateAccountDetailsFormContainer() {
<UpdateAccountDetailsForm
displayName={user.data.name ?? ''}
userId={user.data.id}
onUpdate={invalidateUserDataQuery}
onUpdate={revalidateUserDataQuery}
/>
);
}

View File

@@ -8,7 +8,7 @@ import { Logger } from '@kit/shared/logger';
import { requireAuth } from '@kit/supabase/require-auth';
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
import { PersonalAccountsService } from './services/personal-accounts.service';
import { DeletePersonalAccountService } from './services/delete-personal-account.service';
const emailSettings = getEmailSettingsFromEnvironment();
@@ -41,7 +41,7 @@ export async function deletePersonalAccountAction(formData: FormData) {
const userEmail = session.data.user.email ?? null;
// create a new instance of the personal accounts service
const service = new PersonalAccountsService(client);
const service = new DeletePersonalAccountService();
// delete the user's account and cancel all subscriptions
await service.deletePersonalAccount({

View File

@@ -1,27 +1,28 @@
import { SupabaseClient } from '@supabase/supabase-js';
import { BillingGatewayService } from '@kit/billing-gateway';
import { AccountBillingService } from '@kit/billing-gateway';
import { Mailer } from '@kit/mailers';
import { Logger } from '@kit/shared/logger';
import { Database } from '@kit/supabase/database';
/**
* @name PersonalAccountsService
* @name DeletePersonalAccountService
* @description Service for managing accounts in the application
* @param Database - The Supabase database type to use
* @example
* const client = getSupabaseClient();
* const accountsService = new AccountsService(client);
* const accountsService = new DeletePersonalAccountService();
*/
export class PersonalAccountsService {
private namespace = 'account';
constructor(private readonly client: SupabaseClient<Database>) {}
export class DeletePersonalAccountService {
private namespace = 'accounts.delete';
/**
* @name deletePersonalAccount
* Delete personal account of a user.
* This will delete the user from the authentication provider and cancel all subscriptions.
*
* Permissions are not checked here, as they are checked in the server action.
* USE WITH CAUTION. THE USER MUST HAVE THE NECESSARY PERMISSIONS.
*/
async deletePersonalAccount(params: {
adminClient: SupabaseClient<Database>;
@@ -39,6 +40,11 @@ export class PersonalAccountsService {
'User requested deletion. Processing...',
);
// Cancel all user subscriptions
const billingService = new AccountBillingService(params.adminClient);
await billingService.cancelAllAccountSubscriptions(params.userId);
// execute the deletion of the user
try {
await params.adminClient.auth.admin.deleteUser(params.userId);
@@ -55,17 +61,6 @@ export class PersonalAccountsService {
throw new Error('Error deleting user');
}
// Cancel all user subscriptions
try {
await this.cancelAllUserSubscriptions(params.userId);
} catch (error) {
Logger.error({
userId: params.userId,
error,
name: this.namespace,
});
}
// Send account deletion email
if (params.userEmail) {
try {
@@ -117,53 +112,4 @@ export class PersonalAccountsService {
html,
});
}
private async cancelAllUserSubscriptions(userId: string) {
Logger.info(
{
userId,
name: this.namespace,
},
'Cancelling all subscriptions for user...',
);
const { data: subscriptions } = await this.client
.from('subscriptions')
.select('*')
.eq('account_id', userId);
const cancellationRequests = [];
Logger.info(
{
userId,
subscriptions: subscriptions?.length ?? 0,
name: this.namespace,
},
'Cancelling subscriptions...',
);
for (const subscription of subscriptions ?? []) {
const gateway = new BillingGatewayService(subscription.billing_provider);
cancellationRequests.push(
gateway.cancelSubscription({
subscriptionId: subscription.id,
invoiceNow: true,
}),
);
}
// execute all cancellation requests
await Promise.all(cancellationRequests);
Logger.info(
{
userId,
subscriptions: subscriptions?.length ?? 0,
name: this.namespace,
},
'Subscriptions cancelled successfully',
);
}
}