Update dependencies and enhance admin account page
This commit updates various dependencies in pnpm-lock file and introduces enhancements to the admin account page. This includes adding several new functionality like 'Delete User', 'Ban User', 'Impersonate User' and 'Delete Account'. Various UI components are also added for these features.
This commit is contained in:
@@ -1,10 +1,29 @@
|
||||
import { Database } from '@kit/supabase/database';
|
||||
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
||||
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 { PageBody, PageHeader } from '@kit/ui/page';
|
||||
import { ProfileAvatar } from '@kit/ui/profile-avatar';
|
||||
import { Separator } from '@kit/ui/separator';
|
||||
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 Db = Database['public']['Tables'];
|
||||
type Account = Db['accounts']['Row'];
|
||||
@@ -21,21 +40,71 @@ export function AdminAccountPage(props: {
|
||||
}
|
||||
|
||||
async function PersonalAccountPage(props: { account: Account }) {
|
||||
const client = getSupabaseServerComponentClient({
|
||||
admin: true,
|
||||
});
|
||||
|
||||
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 (
|
||||
<>
|
||||
<PageHeader
|
||||
title={props.account.name}
|
||||
description={`Manage ${props.account.name}'s account details and settings.`}
|
||||
/>
|
||||
title={
|
||||
<div className={'flex items-center space-x-2.5'}>
|
||||
<ProfileAvatar
|
||||
pictureUrl={props.account.picture_url}
|
||||
displayName={props.account.name}
|
||||
/>
|
||||
|
||||
<span>{props.account.name}</span>
|
||||
|
||||
<Badge variant={'outline'}>Personal Account</Badge>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={'flex space-x-2'}>
|
||||
<AdminImpersonateUserDialog userId={props.account.id}>
|
||||
<Button variant={'ghost'}>Impersonate</Button>
|
||||
</AdminImpersonateUserDialog>
|
||||
|
||||
<If condition={isBanned}>
|
||||
<AdminReactivateUserDialog userId={props.account.id}>
|
||||
<Button variant={'ghost'}>Reactivate</Button>
|
||||
</AdminReactivateUserDialog>
|
||||
</If>
|
||||
|
||||
<If condition={!isBanned}>
|
||||
<AdminBanUserDialog userId={props.account.id}>
|
||||
<Button variant={'ghost'}>Ban</Button>
|
||||
</AdminBanUserDialog>
|
||||
</If>
|
||||
|
||||
<AdminDeleteUserDialog userId={props.account.id}>
|
||||
<Button variant={'destructive'}>Delete</Button>
|
||||
</AdminDeleteUserDialog>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<PageBody>
|
||||
<div className={'divider-divider-x flex flex-col space-y-4'}>
|
||||
<Heading level={4}>Memberships</Heading>
|
||||
<div className={'flex flex-col space-y-8'}>
|
||||
<SubscriptionsTable accountId={props.account.id} />
|
||||
|
||||
<div>
|
||||
<AdminMembershipsTable memberships={memberships} />
|
||||
<div className={'divider-divider-x flex flex-col space-y-2.5'}>
|
||||
<Heading level={6}>
|
||||
This user is a member of the following teams:
|
||||
</Heading>
|
||||
|
||||
<div>
|
||||
<AdminMembershipsTable memberships={memberships} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PageBody>
|
||||
@@ -51,17 +120,180 @@ async function TeamAccountPage(props: {
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title={props.account.name}
|
||||
description={`Manage ${props.account.name}'s account details and settings.`}
|
||||
/>
|
||||
title={
|
||||
<div className={'flex items-center space-x-2.5'}>
|
||||
<ProfileAvatar
|
||||
pictureUrl={props.account.picture_url}
|
||||
displayName={props.account.name}
|
||||
/>
|
||||
|
||||
<span>{props.account.name}</span>
|
||||
|
||||
<Badge variant={'outline'}>Team Account</Badge>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<AdminDeleteAccountDialog accountId={props.account.id}>
|
||||
<Button variant={'destructive'}>Delete</Button>
|
||||
</AdminDeleteAccountDialog>
|
||||
</PageHeader>
|
||||
|
||||
<PageBody>
|
||||
<AdminMembersTable members={members} />
|
||||
<div className={'flex flex-col space-y-8'}>
|
||||
<SubscriptionsTable accountId={props.account.id} />
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className={'flex flex-col space-y-2.5'}>
|
||||
<Heading level={6}>This team has the following members:</Heading>
|
||||
|
||||
<AdminMembersTable members={members} />
|
||||
</div>
|
||||
</div>
|
||||
</PageBody>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
async function SubscriptionsTable(props: { accountId: string }) {
|
||||
const client = getSupabaseServerComponentClient({
|
||||
admin: true,
|
||||
});
|
||||
|
||||
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={<>This account does not have an active subscription.</>}
|
||||
>
|
||||
{(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 = getSupabaseServerComponentClient({
|
||||
admin: true,
|
||||
|
||||
Reference in New Issue
Block a user