Files
myeasycms-v2/packages/features/admin/src/components/admin-account-page.tsx
gbuomprisco 089fd37ece Refactor Supabase type imports for improved clarity
Replaced specific object paths with generic type helpers across several files to enhance readability and maintainability. This change standardizes the import patterns and type definitions for database tables and enums.
2024-08-16 12:48:29 +02:00

378 lines
11 KiB
TypeScript

import {
BadgeX,
Ban,
CreditCardIcon,
ShieldPlus,
VenetianMask,
} from 'lucide-react';
import { Tables } from '@kit/supabase/database';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import { Heading } from '@kit/ui/heading';
import { If } from '@kit/ui/if';
import { ProfileAvatar } from '@kit/ui/profile-avatar';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@kit/ui/table';
import { AdminBanUserDialog } from './admin-ban-user-dialog';
import { AdminDeleteAccountDialog } from './admin-delete-account-dialog';
import { AdminDeleteUserDialog } from './admin-delete-user-dialog';
import { AdminImpersonateUserDialog } from './admin-impersonate-user-dialog';
import { AdminMembersTable } from './admin-members-table';
import { AdminMembershipsTable } from './admin-memberships-table';
import { AdminReactivateUserDialog } from './admin-reactivate-user-dialog';
type Account = Tables<'accounts'>;
type Membership = Tables<'accounts_memberships'>;
export function AdminAccountPage(props: {
account: Account & { memberships: Membership[] };
}) {
const isPersonalAccount = props.account.is_personal_account;
if (isPersonalAccount) {
return <PersonalAccountPage account={props.account} />;
}
return <TeamAccountPage account={props.account} />;
}
async function PersonalAccountPage(props: { account: Account }) {
const client = getSupabaseServerAdminClient();
const memberships = await getMemberships(props.account.id);
const { data, error } = await client.auth.admin.getUserById(props.account.id);
if (!data || error) {
throw new Error(`User not found`);
}
const isBanned =
'banned_until' in data.user && data.user.banned_until !== 'none';
return (
<div className={'flex flex-col space-y-4'}>
<AppBreadcrumbs
values={{
[props.account.id]:
props.account.name ?? props.account.email ?? 'Account',
}}
/>
<div className={'flex items-center justify-between'}>
<div className={'flex items-center space-x-4'}>
<div className={'flex items-center space-x-2.5'}>
<ProfileAvatar
pictureUrl={props.account.picture_url}
displayName={props.account.name}
/>
<span className={'text-sm font-semibold capitalize'}>
{props.account.name}
</span>
</div>
<Badge variant={'outline'}>Personal Account</Badge>
<If condition={isBanned}>
<Badge variant={'destructive'}>Banned</Badge>
</If>
</div>
<div className={'flex space-x-1'}>
<If condition={isBanned}>
<AdminReactivateUserDialog userId={props.account.id}>
<Button size={'sm'} variant={'ghost'}>
<ShieldPlus className={'mr-1 h-4'} />
Reactivate
</Button>
</AdminReactivateUserDialog>
</If>
<If condition={!isBanned}>
<AdminBanUserDialog userId={props.account.id}>
<Button size={'sm'} variant={'ghost'}>
<Ban className={'mr-1 h-4'} />
Ban
</Button>
</AdminBanUserDialog>
<AdminImpersonateUserDialog userId={props.account.id}>
<Button size={'sm'} variant={'ghost'}>
<VenetianMask className={'mr-1 h-4'} />
Impersonate
</Button>
</AdminImpersonateUserDialog>
</If>
<AdminDeleteUserDialog userId={props.account.id}>
<Button size={'sm'} variant={'destructive'}>
<BadgeX className={'mr-1 h-4'} />
Delete
</Button>
</AdminDeleteUserDialog>
</div>
</div>
<div className={'flex flex-col space-y-8'}>
<SubscriptionsTable accountId={props.account.id} />
<div className={'divider-divider-x flex flex-col space-y-2.5'}>
<Heading level={6}>Teams</Heading>
<div>
<AdminMembershipsTable memberships={memberships} />
</div>
</div>
</div>
</div>
);
}
async function TeamAccountPage(props: {
account: Account & { memberships: Membership[] };
}) {
const members = await getMembers(props.account.slug ?? '');
return (
<div className={'flex flex-col space-y-4'}>
<AppBreadcrumbs
values={{
[props.account.id]:
props.account.name ?? props.account.email ?? 'Account',
}}
/>
<div className={'flex justify-between'}>
<div className={'flex items-center space-x-4'}>
<div className={'flex items-center space-x-2.5'}>
<ProfileAvatar
pictureUrl={props.account.picture_url}
displayName={props.account.name}
/>
<span className={'text-sm font-semibold capitalize'}>
{props.account.name}
</span>
</div>
<Badge variant={'outline'}>Team Account</Badge>
</div>
<AdminDeleteAccountDialog accountId={props.account.id}>
<Button size={'sm'} variant={'destructive'}>
<BadgeX className={'mr-1 h-4'} />
Delete
</Button>
</AdminDeleteAccountDialog>
</div>
<div>
<div className={'flex flex-col space-y-8'}>
<SubscriptionsTable accountId={props.account.id} />
<div className={'flex flex-col space-y-2.5'}>
<Heading level={6}>Team Members</Heading>
<AdminMembersTable members={members} />
</div>
</div>
</div>
</div>
);
}
async function SubscriptionsTable(props: { accountId: string }) {
const client = getSupabaseServerAdminClient();
const { data: subscription, error } = await client
.from('subscriptions')
.select('*, subscription_items !inner (*)')
.eq('account_id', props.accountId)
.maybeSingle();
if (error) {
return (
<Alert variant={'destructive'}>
<AlertTitle>There was an error loading subscription.</AlertTitle>
<AlertDescription>
Please check the logs for more information or try again later.
</AlertDescription>
</Alert>
);
}
return (
<div className={'flex flex-col space-y-2.5'}>
<Heading level={6}>Subscription</Heading>
<If
condition={subscription}
fallback={
<Alert variant={'warning'}>
<CreditCardIcon className={'h-4'} />
<AlertTitle>No subscription found for this account.</AlertTitle>
<AlertDescription>
This account does not have a subscription.
</AlertDescription>
</Alert>
}
>
{(subscription) => {
return (
<div className={'flex flex-col space-y-4'}>
<Table>
<TableHeader>
<TableHead>Subscription ID</TableHead>
<TableHead>Provider</TableHead>
<TableHead>Customer ID</TableHead>
<TableHead>Status</TableHead>
<TableHead>Created At</TableHead>
<TableHead>Period Starts At</TableHead>
<TableHead>Ends At</TableHead>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>
<span>{subscription.id}</span>
</TableCell>
<TableCell>
<span>{subscription.billing_provider}</span>
</TableCell>
<TableCell>
<span>{subscription.billing_customer_id}</span>
</TableCell>
<TableCell>
<span>{subscription.status}</span>
</TableCell>
<TableCell>
<span>{subscription.created_at}</span>
</TableCell>
<TableCell>
<span>{subscription.period_starts_at}</span>
</TableCell>
<TableCell>
<span>{subscription.period_ends_at}</span>
</TableCell>
</TableRow>
</TableBody>
</Table>
<Table>
<TableHeader>
<TableHead>Product ID</TableHead>
<TableHead>Variant ID</TableHead>
<TableHead>Quantity</TableHead>
<TableHead>Price</TableHead>
<TableHead>Interval</TableHead>
<TableHead>Type</TableHead>
</TableHeader>
<TableBody>
{subscription.subscription_items.map((item) => {
return (
<TableRow key={item.variant_id}>
<TableCell>
<span>{item.product_id}</span>
</TableCell>
<TableCell>
<span>{item.variant_id}</span>
</TableCell>
<TableCell>
<span>{item.quantity}</span>
</TableCell>
<TableCell>
<span>{item.price_amount}</span>
</TableCell>
<TableCell>
<span>{item.interval}</span>
</TableCell>
<TableCell>
<span>{item.type}</span>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
);
}}
</If>
</div>
);
}
async function getMemberships(userId: string) {
const client = getSupabaseServerAdminClient();
const memberships = await client
.from('accounts_memberships')
.select<
string,
Membership & {
account: {
id: string;
name: string;
};
}
>('*, account: account_id !inner (id, name)')
.eq('user_id', userId);
if (memberships.error) {
throw memberships.error;
}
return memberships.data;
}
async function getMembers(accountSlug: string) {
const client = getSupabaseServerAdminClient();
const members = await client.rpc('get_account_members', {
account_slug: accountSlug,
});
if (members.error) {
throw members.error;
}
return members.data;
}