This commit includes a major refactor of UI components to enhance the overall look and feel of the platform. The PageHeader component has been replaced by AccountLayoutHeader across multiple pages including settings, members, and billing. The new AccountLayoutHeader also includes an additional `account` prop, enhancing the component's functionality. Furthermore, this commit includes modifications to the marketing page styling, ensuring better spacing and alignment of features.
194 lines
5.2 KiB
TypeScript
194 lines
5.2 KiB
TypeScript
import { SupabaseClient } from '@supabase/supabase-js';
|
|
|
|
import { PlusCircle } from 'lucide-react';
|
|
|
|
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
|
|
import {
|
|
AccountInvitationsTable,
|
|
AccountMembersTable,
|
|
InviteMembersDialogContainer,
|
|
} from '@kit/team-accounts/components';
|
|
import { Button } from '@kit/ui/button';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from '@kit/ui/card';
|
|
import { If } from '@kit/ui/if';
|
|
import { PageBody } from '@kit/ui/page';
|
|
import { Trans } from '@kit/ui/trans';
|
|
|
|
import { Database } from '~/lib/database.types';
|
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
|
|
|
import { AccountLayoutHeader } from '../_components/account-layout-header';
|
|
import { loadTeamWorkspace } from '../_lib/server/team-account-workspace.loader';
|
|
|
|
interface Params {
|
|
params: {
|
|
account: string;
|
|
};
|
|
}
|
|
|
|
async function loadUser(client: SupabaseClient<Database>) {
|
|
const { data, error } = await client.auth.getUser();
|
|
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
|
|
return data.user;
|
|
}
|
|
|
|
async function loadAccountMembers(
|
|
client: SupabaseClient<Database>,
|
|
account: string,
|
|
) {
|
|
const { data, error } = await client.rpc('get_account_members', {
|
|
account_slug: account,
|
|
});
|
|
|
|
if (error) {
|
|
console.error(error);
|
|
throw error;
|
|
}
|
|
|
|
return data ?? [];
|
|
}
|
|
|
|
async function loadInvitations(
|
|
client: SupabaseClient<Database>,
|
|
account: string,
|
|
) {
|
|
const { data, error } = await client.rpc('get_account_invitations', {
|
|
account_slug: account,
|
|
});
|
|
|
|
if (error) {
|
|
console.error(error);
|
|
throw error;
|
|
}
|
|
|
|
return data ?? [];
|
|
}
|
|
|
|
async function loadData(client: SupabaseClient<Database>, slug: string) {
|
|
return Promise.all([
|
|
loadTeamWorkspace(slug),
|
|
loadAccountMembers(client, slug),
|
|
loadInvitations(client, slug),
|
|
loadUser(client),
|
|
]);
|
|
}
|
|
|
|
export const generateMetadata = async () => {
|
|
const i18n = await createI18nServerInstance();
|
|
const title = i18n.t('teams:members.pageTitle');
|
|
|
|
return {
|
|
title,
|
|
};
|
|
};
|
|
|
|
async function TeamAccountMembersPage({ params }: Params) {
|
|
const client = getSupabaseServerComponentClient();
|
|
|
|
const [{ account }, members, invitations, user] = await loadData(
|
|
client,
|
|
params.account,
|
|
);
|
|
|
|
const canManageRoles = account.permissions.includes('roles.manage');
|
|
const canManageInvitations = account.permissions.includes('invites.manage');
|
|
|
|
const isPrimaryOwner = account.primary_owner_user_id === user.id;
|
|
const currentUserRoleHierarchy = account.role_hierarchy_level;
|
|
|
|
return (
|
|
<>
|
|
<AccountLayoutHeader
|
|
title={<Trans i18nKey={'common:membersTabLabel'} />}
|
|
description={<Trans i18nKey={'common:membersTabDescription'} />}
|
|
account={params.account}
|
|
/>
|
|
|
|
<PageBody>
|
|
<div
|
|
className={'mx-auto flex w-full max-w-3xl flex-col space-y-6 pb-32'}
|
|
>
|
|
<Card>
|
|
<CardHeader className={'flex flex-row justify-between'}>
|
|
<div className={'flex flex-col space-y-1.5'}>
|
|
<CardTitle>
|
|
<Trans i18nKey={'common:membersTabLabel'} />
|
|
</CardTitle>
|
|
|
|
<CardDescription>
|
|
<Trans i18nKey={'common:membersTabDescription'} />
|
|
</CardDescription>
|
|
</div>
|
|
|
|
<If condition={canManageInvitations}>
|
|
<InviteMembersDialogContainer
|
|
userRoleHierarchy={currentUserRoleHierarchy}
|
|
accountId={account.id}
|
|
accountSlug={account.slug}
|
|
>
|
|
<Button size={'sm'} data-test={'invite-members-form-trigger'}>
|
|
<PlusCircle className={'mr-2 w-4'} />
|
|
|
|
<span>
|
|
<Trans i18nKey={'teams:inviteMembersButton'} />
|
|
</span>
|
|
</Button>
|
|
</InviteMembersDialogContainer>
|
|
</If>
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
<AccountMembersTable
|
|
userRoleHierarchy={currentUserRoleHierarchy}
|
|
currentUserId={user.id}
|
|
currentAccountId={account.id}
|
|
members={members}
|
|
isPrimaryOwner={isPrimaryOwner}
|
|
canManageRoles={canManageRoles}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className={'flex flex-row justify-between'}>
|
|
<div className={'flex flex-col space-y-1.5'}>
|
|
<CardTitle>
|
|
<Trans i18nKey={'teams:pendingInvitesHeading'} />
|
|
</CardTitle>
|
|
|
|
<CardDescription>
|
|
<Trans i18nKey={'teams:pendingInvitesDescription'} />
|
|
</CardDescription>
|
|
</div>
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
<AccountInvitationsTable
|
|
permissions={{
|
|
canUpdateInvitation: canManageRoles,
|
|
canRemoveInvitation: canManageRoles,
|
|
currentUserRoleHierarchy,
|
|
}}
|
|
invitations={invitations}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</PageBody>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default withI18n(TeamAccountMembersPage);
|