Update user authentication and sidebar config

This commit involves improving the user authentication process in the billing and accounts pages for better reliability. It also changes the name of 'organization-account-sidebar.config' to 'team-account-sidebar.config' for improved clarity and consistency.
This commit is contained in:
giancarlo
2024-03-28 21:30:12 +08:00
parent 348eec8495
commit efd2a757ff
10 changed files with 69 additions and 40 deletions

View File

@@ -1,5 +1,3 @@
import { redirect } from 'next/navigation';
import { SupabaseClient } from '@supabase/supabase-js'; import { SupabaseClient } from '@supabase/supabase-js';
import { import {
@@ -14,23 +12,15 @@ import { Trans } from '@kit/ui/trans';
import { createPersonalAccountBillingPortalSession } from '~/(dashboard)/home/(user)/billing/server-actions'; import { createPersonalAccountBillingPortalSession } from '~/(dashboard)/home/(user)/billing/server-actions';
import billingConfig from '~/config/billing.config'; import billingConfig from '~/config/billing.config';
import pathsConfig from '~/config/paths.config';
import { withI18n } from '~/lib/i18n/with-i18n'; import { withI18n } from '~/lib/i18n/with-i18n';
import { loadUserWorkspace } from '../../_lib/load-user-workspace';
import { PersonalAccountCheckoutForm } from './_components/personal-account-checkout-form'; import { PersonalAccountCheckoutForm } from './_components/personal-account-checkout-form';
type Subscription = Database['public']['Tables']['subscriptions']['Row']; type Subscription = Database['public']['Tables']['subscriptions']['Row'];
async function PersonalAccountBillingPage() { async function PersonalAccountBillingPage() {
const client = getSupabaseServerComponentClient(); const client = getSupabaseServerComponentClient();
const { session } = await loadUserWorkspace(); const [subscription, customerId] = await loadData(client);
if (!session?.user) {
redirect(pathsConfig.auth.signIn);
}
const [subscription, customerId] = await loadData(client, session.user.id);
return ( return (
<> <>
@@ -68,18 +58,26 @@ async function PersonalAccountBillingPage() {
export default withI18n(PersonalAccountBillingPage); export default withI18n(PersonalAccountBillingPage);
function loadData(client: SupabaseClient<Database>, userId: string) { async function loadData(client: SupabaseClient<Database>) {
const { data, error } = await client.auth.getUser();
if (error ?? !data?.user) {
throw new Error('Authentication required');
}
const user = data.user;
const subscription = client const subscription = client
.from('subscriptions') .from('subscriptions')
.select<string, Subscription>('*') .select<string, Subscription>('*')
.eq('account_id', userId) .eq('account_id', user.id)
.maybeSingle() .maybeSingle()
.then(({ data }) => data); .then(({ data }) => data);
const customer = client const customer = client
.from('billing_customers') .from('billing_customers')
.select('customer_id') .select('customer_id')
.eq('account_id', userId) .eq('account_id', user.id)
.maybeSingle() .maybeSingle()
.then(({ data }) => data?.customer_id); .then(({ data }) => data?.customer_id);

View File

@@ -3,7 +3,7 @@
import { SidebarDivider, SidebarGroup, SidebarItem } from '@kit/ui/sidebar'; import { SidebarDivider, SidebarGroup, SidebarItem } from '@kit/ui/sidebar';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import { getOrganizationAccountSidebarConfig } from '~/config/organization-account-sidebar.config'; import { getOrganizationAccountSidebarConfig } from '~/config/team-account-sidebar.config';
export function AppSidebarNavigation({ export function AppSidebarNavigation({
account, account,

View File

@@ -24,8 +24,8 @@ import {
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import featureFlagsConfig from '~/config/feature-flags.config'; import featureFlagsConfig from '~/config/feature-flags.config';
import { getOrganizationAccountSidebarConfig } from '~/config/organization-account-sidebar.config';
import pathsConfig from '~/config/paths.config'; import pathsConfig from '~/config/paths.config';
import { getOrganizationAccountSidebarConfig } from '~/config/team-account-sidebar.config';
const features = { const features = {
enableTeamAccounts: featureFlagsConfig.enableTeamAccounts, enableTeamAccounts: featureFlagsConfig.enableTeamAccounts,

View File

@@ -1,5 +1,8 @@
import { SupabaseClient } from '@supabase/supabase-js';
import { PlusCircle } from 'lucide-react'; import { PlusCircle } from 'lucide-react';
import { Database } from '@kit/supabase/database';
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client'; import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
import { import {
AccountInvitationsTable, AccountInvitationsTable,
@@ -26,9 +29,20 @@ interface Params {
}; };
} }
async function loadAccountMembers(account: string) { async function loadUser(client: SupabaseClient<Database>) {
const client = getSupabaseServerComponentClient(); 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', { const { data, error } = await client.rpc('get_account_members', {
account_slug: account, account_slug: account,
}); });
@@ -41,9 +55,10 @@ async function loadAccountMembers(account: string) {
return data ?? []; return data ?? [];
} }
async function loadInvitations(account: string) { async function loadInvitations(
const client = getSupabaseServerComponentClient(); client: SupabaseClient<Database>,
account: string,
) {
const { data, error } = await client.rpc('get_account_invitations', { const { data, error } = await client.rpc('get_account_invitations', {
account_slug: account, account_slug: account,
}); });
@@ -56,14 +71,22 @@ async function loadInvitations(account: string) {
return data ?? []; return data ?? [];
} }
async function TeamAccountMembersPage({ params }: Params) { async function loadData(client: SupabaseClient<Database>, slug: string) {
const slug = params.account; return Promise.all([
const [{ account, user }, members, invitations] = await Promise.all([
loadTeamWorkspace(slug), loadTeamWorkspace(slug),
loadAccountMembers(slug), loadAccountMembers(client, slug),
loadInvitations(slug), loadInvitations(client, slug),
loadUser(client),
]); ]);
}
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 canManageRoles = account.permissions.includes('roles.manage');
const isPrimaryOwner = account.primary_owner_user_id === user.id; const isPrimaryOwner = account.primary_owner_user_id === user.id;

View File

@@ -7,7 +7,7 @@ import pathsConfig from '~/config/paths.config';
const iconClasses = 'w-4'; const iconClasses = 'w-4';
const routes = (account: string) => [ const getRoutes = (account: string) => [
{ {
label: 'common:dashboardTabLabel', label: 'common:dashboardTabLabel',
path: pathsConfig.app.accountHome.replace('[account]', account), path: pathsConfig.app.accountHome.replace('[account]', account),
@@ -41,7 +41,7 @@ const routes = (account: string) => [
export function getOrganizationAccountSidebarConfig(account: string) { export function getOrganizationAccountSidebarConfig(account: string) {
return SidebarConfigSchema.parse({ return SidebarConfigSchema.parse({
routes: routes(account), routes: getRoutes(account),
}); });
} }

View File

@@ -9,8 +9,10 @@
"dangerZone": "Danger Zone", "dangerZone": "Danger Zone",
"dangerZoneDescription": "This section contains actions that are irreversible" "dangerZoneDescription": "This section contains actions that are irreversible"
}, },
"yourTeam": "Your Teams", "yourTeams": "Your Teams",
"createTeam": "Create Team", "createTeam": "Create a Team",
"personalAccount": "Personal Account",
"searchAccount": "Search Account...",
"membersTabLabel": "Members", "membersTabLabel": "Members",
"memberName": "Name", "memberName": "Name",
"youLabel": "You", "youLabel": "You",

View File

@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import { CaretSortIcon, PersonIcon } from '@radix-ui/react-icons'; import { CaretSortIcon, PersonIcon } from '@radix-ui/react-icons';
import { Check, Plus } from 'lucide-react'; import { Check, Plus } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Avatar, AvatarFallback, AvatarImage } from '@kit/ui/avatar'; import { Avatar, AvatarFallback, AvatarImage } from '@kit/ui/avatar';
import { Button } from '@kit/ui/button'; import { Button } from '@kit/ui/button';
@@ -59,6 +60,8 @@ export function AccountSelector({
selectedAccount ?? PERSONAL_ACCOUNT_SLUG, selectedAccount ?? PERSONAL_ACCOUNT_SLUG,
); );
const { t } = useTranslation('teams');
useEffect(() => { useEffect(() => {
setValue(selectedAccount ?? PERSONAL_ACCOUNT_SLUG); setValue(selectedAccount ?? PERSONAL_ACCOUNT_SLUG);
}, [selectedAccount]); }, [selectedAccount]);
@@ -105,7 +108,7 @@ export function AccountSelector({
hidden: collapsed, hidden: collapsed,
})} })}
> >
Personal Account <Trans i18nKey={'teams:personalAccount'} />
</span> </span>
</span> </span>
} }
@@ -137,7 +140,7 @@ export function AccountSelector({
<PopoverContent className="w-full p-0"> <PopoverContent className="w-full p-0">
<Command> <Command>
<CommandInput placeholder="Search account..." className="h-9" /> <CommandInput placeholder={t('searchAccount')} className="h-9" />
<CommandList> <CommandList>
<CommandGroup> <CommandGroup>
@@ -147,7 +150,9 @@ export function AccountSelector({
> >
<PersonIcon className="mr-2 h-4 w-4" /> <PersonIcon className="mr-2 h-4 w-4" />
<span>Personal Account</span> <span>
<Trans i18nKey={'teams:personalAccount'} />
</span>
<Icon item={PERSONAL_ACCOUNT_SLUG} /> <Icon item={PERSONAL_ACCOUNT_SLUG} />
</CommandItem> </CommandItem>

View File

@@ -364,7 +364,7 @@ function FactorNameForm(
}} }}
/> />
<div className={'flex space-x-2'}> <div className={'flex justify-end space-x-2'}>
<Button type={'button'} variant={'ghost'} onClick={props.onCancel}> <Button type={'button'} variant={'ghost'} onClick={props.onCancel}>
<Trans i18nKey={'common:cancel'} /> <Trans i18nKey={'common:cancel'} />
</Button> </Button>

View File

@@ -3,16 +3,16 @@ import { useCallback } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useSupabase } from '@kit/supabase/hooks/use-supabase'; import { useSupabase } from '@kit/supabase/hooks/use-supabase';
import { useUser } from '@kit/supabase/hooks/use-user';
const queryKey = ['personal-account:data']; const queryKey = ['personal-account:data'];
export function usePersonalAccountData() { export function usePersonalAccountData() {
const client = useSupabase(); const client = useSupabase();
const user = useUser();
const queryFn = async () => { const queryFn = async () => {
const { data, error } = await client.auth.getSession(); if (!user.data?.id) {
if (!data.session || error) {
return null; return null;
} }
@@ -25,7 +25,7 @@ export function usePersonalAccountData() {
picture_url picture_url
`, `,
) )
.eq('primary_owner_user_id', data.session.user.id) .eq('primary_owner_user_id', user.data?.id)
.eq('is_personal_account', true) .eq('is_personal_account', true)
.single(); .single();
@@ -39,6 +39,7 @@ export function usePersonalAccountData() {
return useQuery({ return useQuery({
queryKey, queryKey,
queryFn, queryFn,
enabled: !!user.data?.id,
}); });
} }

View File

@@ -12,7 +12,7 @@ export class AccountMembersService {
.from('accounts_memberships') .from('accounts_memberships')
.delete() .delete()
.match({ .match({
id: params.accountId, account_id: params.accountId,
user_id: params.userId, user_id: params.userId,
}); });