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:
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user