Refactor account handling to improve performance
This commit dates the transition from a global user session to individual account handling based on user ID. The transition was made across several components, notably the account settings, icons, and selector. This change improves performance by reducing unnecessary requests and ensures more accurate data handling. The commit also includes some cleanups and minor fixes spread across different components.
This commit is contained in:
@@ -18,6 +18,7 @@ export function HomeAccountSelector(props: {
|
|||||||
image: string | null;
|
image: string | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
userId: string;
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -27,6 +28,7 @@ export function HomeAccountSelector(props: {
|
|||||||
collapsed={props.collapsed}
|
collapsed={props.collapsed}
|
||||||
accounts={props.accounts}
|
accounts={props.accounts}
|
||||||
features={features}
|
features={features}
|
||||||
|
userId={props.userId}
|
||||||
onAccountChange={(value) => {
|
onAccountChange={(value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
const path = pathsConfig.app.accountHome.replace('[account]', value);
|
const path = pathsConfig.app.accountHome.replace('[account]', value);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { personalAccountNavigationConfig } from '~/config/personal-account-navig
|
|||||||
// home imports
|
// home imports
|
||||||
import { HomeAccountSelector } from '../_components/home-account-selector';
|
import { HomeAccountSelector } from '../_components/home-account-selector';
|
||||||
import { UserNotifications } from '../_components/user-notifications';
|
import { UserNotifications } from '../_components/user-notifications';
|
||||||
import { type UserWorkspace } from '../_lib/server/load-user-workspace';
|
import { type UserWorkspace } from '../_lib/server/user-workspace.loader';
|
||||||
|
|
||||||
export function HomeMenuNavigation(props: { workspace: UserWorkspace }) {
|
export function HomeMenuNavigation(props: { workspace: UserWorkspace }) {
|
||||||
const { workspace, user, accounts } = props.workspace;
|
const { workspace, user, accounts } = props.workspace;
|
||||||
@@ -50,7 +50,11 @@ export function HomeMenuNavigation(props: { workspace: UserWorkspace }) {
|
|||||||
|
|
||||||
<div className={'flex justify-end space-x-2.5'}>
|
<div className={'flex justify-end space-x-2.5'}>
|
||||||
<If condition={featuresFlagConfig.enableTeamAccounts}>
|
<If condition={featuresFlagConfig.enableTeamAccounts}>
|
||||||
<HomeAccountSelector accounts={accounts} collapsed={false} />
|
<HomeAccountSelector
|
||||||
|
userId={user.id}
|
||||||
|
accounts={accounts}
|
||||||
|
collapsed={false}
|
||||||
|
/>
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
<UserNotifications userId={user.id} />
|
<UserNotifications userId={user.id} />
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { personalAccountNavigationConfig } from '~/config/personal-account-navig
|
|||||||
|
|
||||||
// home imports
|
// home imports
|
||||||
import { HomeAccountSelector } from '../_components/home-account-selector';
|
import { HomeAccountSelector } from '../_components/home-account-selector';
|
||||||
import type { UserWorkspace } from '../_lib/server/load-user-workspace';
|
import type { UserWorkspace } from '../_lib/server/user-workspace.loader';
|
||||||
|
|
||||||
export function HomeMobileNavigation(props: { workspace: UserWorkspace }) {
|
export function HomeMobileNavigation(props: { workspace: UserWorkspace }) {
|
||||||
const signOut = useSignOut();
|
const signOut = useSignOut();
|
||||||
@@ -69,6 +69,7 @@ export function HomeMobileNavigation(props: { workspace: UserWorkspace }) {
|
|||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
|
|
||||||
<HomeAccountSelector
|
<HomeAccountSelector
|
||||||
|
userId={props.workspace.user.id}
|
||||||
accounts={props.workspace.accounts}
|
accounts={props.workspace.accounts}
|
||||||
collapsed={false}
|
collapsed={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { personalAccountNavigationConfig } from '~/config/personal-account-navig
|
|||||||
import { UserNotifications } from '~/home/(user)/_components/user-notifications';
|
import { UserNotifications } from '~/home/(user)/_components/user-notifications';
|
||||||
|
|
||||||
// home imports
|
// home imports
|
||||||
import type { UserWorkspace } from '../_lib/server/load-user-workspace';
|
import type { UserWorkspace } from '../_lib/server/user-workspace.loader';
|
||||||
import { HomeAccountSelector } from './home-account-selector';
|
import { HomeAccountSelector } from './home-account-selector';
|
||||||
|
|
||||||
export function HomeSidebar(props: { workspace: UserWorkspace }) {
|
export function HomeSidebar(props: { workspace: UserWorkspace }) {
|
||||||
@@ -22,7 +22,11 @@ export function HomeSidebar(props: { workspace: UserWorkspace }) {
|
|||||||
condition={featuresFlagConfig.enableTeamAccounts}
|
condition={featuresFlagConfig.enableTeamAccounts}
|
||||||
fallback={<AppLogo className={'py-2'} />}
|
fallback={<AppLogo className={'py-2'} />}
|
||||||
>
|
>
|
||||||
<HomeAccountSelector collapsed={false} accounts={accounts} />
|
<HomeAccountSelector
|
||||||
|
userId={user.id}
|
||||||
|
collapsed={false}
|
||||||
|
accounts={accounts}
|
||||||
|
/>
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
<UserNotifications userId={user.id} />
|
<UserNotifications userId={user.id} />
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
|
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
import { createAccountsApi } from '@kit/accounts/api';
|
import { createAccountsApi } from '@kit/accounts/api';
|
||||||
|
import { requireUser } from '@kit/supabase/require-user';
|
||||||
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
|
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
|
||||||
|
|
||||||
import featureFlagsConfig from '~/config/feature-flags.config';
|
import featureFlagsConfig from '~/config/feature-flags.config';
|
||||||
@@ -24,20 +27,19 @@ export const loadUserWorkspace = cache(async () => {
|
|||||||
: () => Promise.resolve([]);
|
: () => Promise.resolve([]);
|
||||||
|
|
||||||
const workspacePromise = api.getAccountWorkspace();
|
const workspacePromise = api.getAccountWorkspace();
|
||||||
const userPromise = client.auth.getUser();
|
|
||||||
|
|
||||||
const [accounts, workspace, userResult] = await Promise.all([
|
const [accounts, workspace, auth] = await Promise.all([
|
||||||
accountsPromise(),
|
accountsPromise(),
|
||||||
workspacePromise,
|
workspacePromise,
|
||||||
userPromise,
|
requireUser(client),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const user = userResult.data.user;
|
if (!auth.data) {
|
||||||
|
return redirect(auth.redirectTo);
|
||||||
if (!user) {
|
|
||||||
throw new Error('User is not logged in');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const user = auth.data;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
workspace,
|
workspace,
|
||||||
@@ -18,7 +18,7 @@ import { withI18n } from '~/lib/i18n/with-i18n';
|
|||||||
import { HomeMenuNavigation } from './_components/home-menu-navigation';
|
import { HomeMenuNavigation } from './_components/home-menu-navigation';
|
||||||
import { HomeMobileNavigation } from './_components/home-mobile-navigation';
|
import { HomeMobileNavigation } from './_components/home-mobile-navigation';
|
||||||
import { HomeSidebar } from './_components/home-sidebar';
|
import { HomeSidebar } from './_components/home-sidebar';
|
||||||
import { loadUserWorkspace } from './_lib/server/load-user-workspace';
|
import { loadUserWorkspace } from './_lib/server/user-workspace.loader';
|
||||||
|
|
||||||
function UserHomeLayout({ children }: React.PropsWithChildren) {
|
function UserHomeLayout({ children }: React.PropsWithChildren) {
|
||||||
const workspace = use(loadUserWorkspace());
|
const workspace = use(loadUserWorkspace());
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { use } from 'react';
|
||||||
|
|
||||||
import { PersonalAccountSettingsContainer } from '@kit/accounts/personal-account-settings';
|
import { PersonalAccountSettingsContainer } from '@kit/accounts/personal-account-settings';
|
||||||
import { PageBody } from '@kit/ui/page';
|
import { PageBody } from '@kit/ui/page';
|
||||||
|
|
||||||
import featureFlagsConfig from '~/config/feature-flags.config';
|
import featureFlagsConfig from '~/config/feature-flags.config';
|
||||||
import pathsConfig from '~/config/paths.config';
|
import pathsConfig from '~/config/paths.config';
|
||||||
|
import { loadUserWorkspace } from '~/home/(user)/_lib/server/user-workspace.loader';
|
||||||
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
@@ -24,10 +27,16 @@ export const generateMetadata = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function PersonalAccountSettingsPage() {
|
function PersonalAccountSettingsPage() {
|
||||||
|
const { user } = use(loadUserWorkspace());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageBody>
|
<PageBody>
|
||||||
<div className={'flex w-full flex-1 flex-col lg:max-w-2xl'}>
|
<div className={'flex w-full flex-1 flex-col lg:max-w-2xl'}>
|
||||||
<PersonalAccountSettingsContainer features={features} paths={paths} />
|
<PersonalAccountSettingsContainer
|
||||||
|
userId={user.id}
|
||||||
|
features={features}
|
||||||
|
paths={paths}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</PageBody>
|
</PageBody>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const features = {
|
|||||||
|
|
||||||
export function TeamAccountAccountsSelector(params: {
|
export function TeamAccountAccountsSelector(params: {
|
||||||
selectedAccount: string;
|
selectedAccount: string;
|
||||||
|
userId: string;
|
||||||
|
|
||||||
accounts: Array<{
|
accounts: Array<{
|
||||||
label: string | null;
|
label: string | null;
|
||||||
value: string | null;
|
value: string | null;
|
||||||
@@ -25,6 +27,7 @@ export function TeamAccountAccountsSelector(params: {
|
|||||||
<AccountSelector
|
<AccountSelector
|
||||||
selectedAccount={params.selectedAccount}
|
selectedAccount={params.selectedAccount}
|
||||||
accounts={params.accounts}
|
accounts={params.accounts}
|
||||||
|
userId={params.userId}
|
||||||
collapsed={false}
|
collapsed={false}
|
||||||
features={features}
|
features={features}
|
||||||
onAccountChange={(value) => {
|
onAccountChange={(value) => {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const features = {
|
|||||||
export const TeamAccountLayoutMobileNavigation = (
|
export const TeamAccountLayoutMobileNavigation = (
|
||||||
props: React.PropsWithChildren<{
|
props: React.PropsWithChildren<{
|
||||||
account: string;
|
account: string;
|
||||||
|
userId: string;
|
||||||
accounts: Accounts;
|
accounts: Accounts;
|
||||||
}>,
|
}>,
|
||||||
) => {
|
) => {
|
||||||
@@ -83,7 +84,7 @@ export const TeamAccountLayoutMobileNavigation = (
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
<DropdownMenuContent sideOffset={10} className={'w-screen rounded-none'}>
|
<DropdownMenuContent sideOffset={10} className={'w-screen rounded-none'}>
|
||||||
<TeamAccountsModal accounts={props.accounts} />
|
<TeamAccountsModal userId={props.userId} accounts={props.accounts} />
|
||||||
|
|
||||||
{Links}
|
{Links}
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ function SignOutDropdownItem(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TeamAccountsModal(props: { accounts: Accounts }) {
|
function TeamAccountsModal(props: { accounts: Accounts; userId: string }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -165,6 +166,7 @@ function TeamAccountsModal(props: { accounts: Accounts }) {
|
|||||||
<div className={'py-16'}>
|
<div className={'py-16'}>
|
||||||
<AccountSelector
|
<AccountSelector
|
||||||
className={'w-full max-w-full'}
|
className={'w-full max-w-full'}
|
||||||
|
userId={props.userId}
|
||||||
onAccountChange={(value) => {
|
onAccountChange={(value) => {
|
||||||
const path = value
|
const path = value
|
||||||
? pathsConfig.app.accountHome.replace('[account]', value)
|
? pathsConfig.app.accountHome.replace('[account]', value)
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ function SidebarContainer(props: {
|
|||||||
collapsible?: boolean;
|
collapsible?: boolean;
|
||||||
user: User;
|
user: User;
|
||||||
}) {
|
}) {
|
||||||
const { account, accounts } = props;
|
const { account, accounts, user } = props;
|
||||||
|
const userId = user.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -68,12 +69,13 @@ function SidebarContainer(props: {
|
|||||||
className={'flex max-w-full items-center justify-between space-x-4'}
|
className={'flex max-w-full items-center justify-between space-x-4'}
|
||||||
>
|
>
|
||||||
<TeamAccountAccountsSelector
|
<TeamAccountAccountsSelector
|
||||||
|
userId={userId}
|
||||||
selectedAccount={account}
|
selectedAccount={account}
|
||||||
accounts={accounts}
|
accounts={accounts}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TeamAccountNotifications
|
<TeamAccountNotifications
|
||||||
userId={props.user.id}
|
userId={userId}
|
||||||
accountId={props.accountId}
|
accountId={props.accountId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export function TeamAccountNavigationMenu(props: {
|
|||||||
|
|
||||||
<div className={'flex justify-end space-x-2.5'}>
|
<div className={'flex justify-end space-x-2.5'}>
|
||||||
<TeamAccountAccountsSelector
|
<TeamAccountAccountsSelector
|
||||||
|
userId={user.id}
|
||||||
selectedAccount={account.slug}
|
selectedAccount={account.slug}
|
||||||
accounts={accounts.map((account) => ({
|
accounts={accounts.map((account) => ({
|
||||||
label: account.name,
|
label: account.name,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { cache } from 'react';
|
|||||||
|
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
|
import { requireUser } from '@kit/supabase/require-user';
|
||||||
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
|
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
|
||||||
import { createTeamAccountsApi } from '@kit/team-accounts/api';
|
import { createTeamAccountsApi } from '@kit/team-accounts/api';
|
||||||
|
|
||||||
@@ -26,19 +27,25 @@ export const loadTeamWorkspace = cache(async (accountSlug: string) => {
|
|||||||
const client = getSupabaseServerComponentClient();
|
const client = getSupabaseServerComponentClient();
|
||||||
const api = createTeamAccountsApi(client);
|
const api = createTeamAccountsApi(client);
|
||||||
|
|
||||||
const workspace = await api.getAccountWorkspace(accountSlug);
|
const [workspace, auth] = await Promise.all([
|
||||||
|
api.getAccountWorkspace(accountSlug),
|
||||||
if (workspace.error) {
|
requireUser(client),
|
||||||
throw workspace.error;
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
const account = workspace.data.account;
|
|
||||||
|
|
||||||
// we cannot find any record for the selected account
|
// we cannot find any record for the selected account
|
||||||
// so we redirect the user to the home page
|
// so we redirect the user to the home page
|
||||||
if (!account) {
|
if (!workspace.data?.account) {
|
||||||
return redirect(pathsConfig.app.home);
|
return redirect(pathsConfig.app.home);
|
||||||
}
|
}
|
||||||
|
|
||||||
return workspace.data;
|
if (!auth.data) {
|
||||||
|
return redirect(auth.redirectTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = auth.data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...workspace.data,
|
||||||
|
user,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ function TeamWorkspaceLayout({
|
|||||||
|
|
||||||
<div className={'flex space-x-4'}>
|
<div className={'flex space-x-4'}>
|
||||||
<TeamAccountLayoutMobileNavigation
|
<TeamAccountLayoutMobileNavigation
|
||||||
|
userId={data.user.id}
|
||||||
accounts={accounts}
|
accounts={accounts}
|
||||||
account={params.account}
|
account={params.account}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export async function loadMembersPageData(
|
|||||||
loadTeamWorkspace(slug),
|
loadTeamWorkspace(slug),
|
||||||
loadAccountMembers(client, slug),
|
loadAccountMembers(client, slug),
|
||||||
loadInvitations(client, slug),
|
loadInvitations(client, slug),
|
||||||
loadUser(client),
|
|
||||||
canAddMember,
|
canAddMember,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -38,16 +37,6 @@ async function canAddMember() {
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadUser(client: SupabaseClient<Database>) {
|
|
||||||
const { data, error } = await client.auth.getUser();
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data.user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load account members
|
* Load account members
|
||||||
* @param client
|
* @param client
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { If } from '@kit/ui/if';
|
|||||||
import { PageBody } from '@kit/ui/page';
|
import { PageBody } from '@kit/ui/page';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
|
import { loadTeamWorkspace } from '~/home/[account]/_lib/server/team-account-workspace.loader';
|
||||||
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
@@ -43,7 +44,9 @@ export const generateMetadata = async () => {
|
|||||||
async function TeamAccountMembersPage({ params }: Params) {
|
async function TeamAccountMembersPage({ params }: Params) {
|
||||||
const client = getSupabaseServerComponentClient();
|
const client = getSupabaseServerComponentClient();
|
||||||
|
|
||||||
const [{ account }, members, invitations, user, canAddMember] =
|
const { user } = await loadTeamWorkspace(params.account);
|
||||||
|
|
||||||
|
const [{ account }, members, invitations, canAddMember] =
|
||||||
await loadMembersPageData(client, params.account);
|
await loadMembersPageData(client, params.account);
|
||||||
|
|
||||||
const canManageRoles = account.permissions.includes('roles.manage');
|
const canManageRoles = account.permissions.includes('roles.manage');
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const features = {
|
|||||||
|
|
||||||
export function ProfileAccountDropdownContainer(props: {
|
export function ProfileAccountDropdownContainer(props: {
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
user: User | null;
|
user: User;
|
||||||
|
|
||||||
account?: {
|
account?: {
|
||||||
id: string | null;
|
id: string | null;
|
||||||
@@ -29,7 +29,7 @@ export function ProfileAccountDropdownContainer(props: {
|
|||||||
}) {
|
}) {
|
||||||
const signOut = useSignOut();
|
const signOut = useSignOut();
|
||||||
const user = useUser(props.user);
|
const user = useUser(props.user);
|
||||||
const userData = user.data ?? props.user ?? null;
|
const userData = user.data as User;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={props.collapsed ? '' : 'w-full'}>
|
<div className={props.collapsed ? '' : 'w-full'}>
|
||||||
|
|||||||
@@ -22,11 +22,6 @@ export const rootMetadata: Metadata = {
|
|||||||
},
|
},
|
||||||
icons: {
|
icons: {
|
||||||
icon: '/images/favicon/favicon.ico',
|
icon: '/images/favicon/favicon.ico',
|
||||||
shortcut: '/shortcut-icon.png',
|
|
||||||
apple: '/images/favicon/apple-touch-icon.png',
|
apple: '/images/favicon/apple-touch-icon.png',
|
||||||
other: {
|
|
||||||
rel: 'apple-touch-icon-precomposed',
|
|
||||||
url: '/apple-touch-icon-precomposed.png',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { NextRequest } from 'next/server';
|
import type { NextRequest } from 'next/server';
|
||||||
import { NextResponse, URLPattern } from 'next/server';
|
import { NextResponse, URLPattern } from 'next/server';
|
||||||
|
|
||||||
import type { UserResponse } from '@supabase/supabase-js';
|
|
||||||
|
|
||||||
import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
|
import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
|
||||||
|
|
||||||
import { checkRequiresMultiFactorAuthentication } from '@kit/supabase/check-requires-mfa';
|
import { checkRequiresMultiFactorAuthentication } from '@kit/supabase/check-requires-mfa';
|
||||||
@@ -15,17 +13,17 @@ const CSRF_SECRET_COOKIE = 'csrfSecret';
|
|||||||
const NEXT_ACTION_HEADER = 'next-action';
|
const NEXT_ACTION_HEADER = 'next-action';
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: [
|
matcher: ['/((?!_next/static|_next/image|images|locales|assets|api/*).*)'],
|
||||||
'/((?!_next/static|_next/image|favicon.ico|locales|assets|api/*).*)',
|
};
|
||||||
],
|
|
||||||
|
const getUser = (request: NextRequest, response: NextResponse) => {
|
||||||
|
const supabase = createMiddlewareClient(request, response);
|
||||||
|
|
||||||
|
return supabase.auth.getUser();
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest) {
|
||||||
const response = NextResponse.next();
|
const response = NextResponse.next();
|
||||||
const supabase = createMiddlewareClient(request, response);
|
|
||||||
|
|
||||||
// get the user from the session (no matter if it's logged in or not)
|
|
||||||
const userResponse = await supabase.auth.getUser();
|
|
||||||
|
|
||||||
// set a unique request ID for each request
|
// set a unique request ID for each request
|
||||||
// this helps us log and trace requests
|
// this helps us log and trace requests
|
||||||
@@ -39,11 +37,7 @@ export async function middleware(request: NextRequest) {
|
|||||||
|
|
||||||
// if a pattern handler exists, call it
|
// if a pattern handler exists, call it
|
||||||
if (handlePattern) {
|
if (handlePattern) {
|
||||||
const patternHandlerResponse = await handlePattern(
|
const patternHandlerResponse = await handlePattern(request, csrfResponse);
|
||||||
request,
|
|
||||||
csrfResponse,
|
|
||||||
userResponse,
|
|
||||||
);
|
|
||||||
|
|
||||||
// if a pattern handler returns a response, return it
|
// if a pattern handler returns a response, return it
|
||||||
if (patternHandlerResponse) {
|
if (patternHandlerResponse) {
|
||||||
@@ -95,18 +89,14 @@ function isServerAction(request: NextRequest) {
|
|||||||
return headers.has(NEXT_ACTION_HEADER);
|
return headers.has(NEXT_ACTION_HEADER);
|
||||||
}
|
}
|
||||||
|
|
||||||
function adminMiddleware(
|
async function adminMiddleware(request: NextRequest, response: NextResponse) {
|
||||||
request: NextRequest,
|
|
||||||
response: NextResponse,
|
|
||||||
userResponse: UserResponse,
|
|
||||||
) {
|
|
||||||
const isAdminPath = request.nextUrl.pathname.startsWith('/admin');
|
const isAdminPath = request.nextUrl.pathname.startsWith('/admin');
|
||||||
|
|
||||||
if (!isAdminPath) {
|
if (!isAdminPath) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = userResponse;
|
const { data, error } = await getUser(request, response);
|
||||||
|
|
||||||
// If user is not logged in, redirect to sign in page.
|
// If user is not logged in, redirect to sign in page.
|
||||||
// This should never happen, but just in case.
|
// This should never happen, but just in case.
|
||||||
@@ -138,12 +128,8 @@ function getPatterns() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: new URLPattern({ pathname: '/auth*' }),
|
pattern: new URLPattern({ pathname: '/auth*' }),
|
||||||
handler: (
|
handler: async (req: NextRequest, res: NextResponse) => {
|
||||||
req: NextRequest,
|
const { data: user } = await getUser(req, res);
|
||||||
_: NextResponse,
|
|
||||||
userResponse: UserResponse,
|
|
||||||
) => {
|
|
||||||
const user = userResponse.data.user;
|
|
||||||
|
|
||||||
// the user is logged out, so we don't need to do anything
|
// the user is logged out, so we don't need to do anything
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -164,14 +150,8 @@ function getPatterns() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: new URLPattern({ pathname: '/home*' }),
|
pattern: new URLPattern({ pathname: '/home*' }),
|
||||||
handler: async (
|
handler: async (req: NextRequest, res: NextResponse) => {
|
||||||
req: NextRequest,
|
const { data: user } = await getUser(req, res);
|
||||||
res: NextResponse,
|
|
||||||
userResponse: UserResponse,
|
|
||||||
) => {
|
|
||||||
const {
|
|
||||||
data: { user },
|
|
||||||
} = userResponse;
|
|
||||||
|
|
||||||
const origin = req.nextUrl.origin;
|
const origin = req.nextUrl.origin;
|
||||||
const next = req.nextUrl.pathname;
|
const next = req.nextUrl.pathname;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ interface AccountSelectorProps {
|
|||||||
enableTeamCreation: boolean;
|
enableTeamCreation: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
userId: string;
|
||||||
selectedAccount?: string;
|
selectedAccount?: string;
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -49,6 +50,7 @@ export function AccountSelector({
|
|||||||
accounts,
|
accounts,
|
||||||
selectedAccount,
|
selectedAccount,
|
||||||
onAccountChange,
|
onAccountChange,
|
||||||
|
userId,
|
||||||
className,
|
className,
|
||||||
features = {
|
features = {
|
||||||
enableTeamCreation: true,
|
enableTeamCreation: true,
|
||||||
@@ -63,7 +65,7 @@ export function AccountSelector({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { t } = useTranslation('teams');
|
const { t } = useTranslation('teams');
|
||||||
const personalData = usePersonalAccountData();
|
const personalData = usePersonalAccountData(userId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(selectedAccount ?? PERSONAL_ACCOUNT_SLUG);
|
setValue(selectedAccount ?? PERSONAL_ACCOUNT_SLUG);
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ export function PersonalAccountDropdown({
|
|||||||
features,
|
features,
|
||||||
account,
|
account,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
user: User;
|
||||||
user: User | null;
|
|
||||||
|
|
||||||
account?: {
|
account?: {
|
||||||
id: string | null;
|
id: string | null;
|
||||||
@@ -48,7 +47,6 @@ export function PersonalAccountDropdown({
|
|||||||
};
|
};
|
||||||
|
|
||||||
signOutRequested: () => unknown;
|
signOutRequested: () => unknown;
|
||||||
showProfileName?: boolean;
|
|
||||||
|
|
||||||
paths: {
|
paths: {
|
||||||
home: string;
|
home: string;
|
||||||
@@ -57,8 +55,14 @@ export function PersonalAccountDropdown({
|
|||||||
features: {
|
features: {
|
||||||
enableThemeToggle: boolean;
|
enableThemeToggle: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
className?: string;
|
||||||
|
showProfileName?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { data: personalAccountData } = usePersonalAccountData(account);
|
const { data: personalAccountData } = usePersonalAccountData(
|
||||||
|
user.id,
|
||||||
|
account,
|
||||||
|
);
|
||||||
|
|
||||||
const signedInAsLabel = useMemo(() => {
|
const signedInAsLabel = useMemo(() => {
|
||||||
const email = user?.email ?? undefined;
|
const email = user?.email ?? undefined;
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import { UpdateAccountImageContainer } from './update-account-image-container';
|
|||||||
|
|
||||||
export function PersonalAccountSettingsContainer(
|
export function PersonalAccountSettingsContainer(
|
||||||
props: React.PropsWithChildren<{
|
props: React.PropsWithChildren<{
|
||||||
|
userId: string;
|
||||||
|
|
||||||
features: {
|
features: {
|
||||||
enableAccountDeletion: boolean;
|
enableAccountDeletion: boolean;
|
||||||
};
|
};
|
||||||
@@ -34,7 +36,7 @@ export function PersonalAccountSettingsContainer(
|
|||||||
}>,
|
}>,
|
||||||
) {
|
) {
|
||||||
const supportsLanguageSelection = useSupportMultiLanguage();
|
const supportsLanguageSelection = useSupportMultiLanguage();
|
||||||
const user = usePersonalAccountData();
|
const user = usePersonalAccountData(props.userId);
|
||||||
|
|
||||||
if (!user.data || user.isPending) {
|
if (!user.data || user.isPending) {
|
||||||
return <LoadingOverlay fullPage />;
|
return <LoadingOverlay fullPage />;
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ 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';
|
|
||||||
|
|
||||||
export function usePersonalAccountData(
|
export function usePersonalAccountData(
|
||||||
|
userId: string,
|
||||||
partialAccount?:
|
partialAccount?:
|
||||||
| {
|
| {
|
||||||
id: string | null;
|
id: string | null;
|
||||||
@@ -15,9 +15,6 @@ export function usePersonalAccountData(
|
|||||||
| undefined,
|
| undefined,
|
||||||
) {
|
) {
|
||||||
const client = useSupabase();
|
const client = useSupabase();
|
||||||
const user = useUser();
|
|
||||||
const userId = user.data?.id;
|
|
||||||
|
|
||||||
const queryKey = ['account:data', userId];
|
const queryKey = ['account:data', userId];
|
||||||
|
|
||||||
const queryFn = async () => {
|
const queryFn = async () => {
|
||||||
|
|||||||
@@ -78,16 +78,9 @@ export class TeamAccountsApi {
|
|||||||
|
|
||||||
const accountsPromise = this.client.from('user_accounts').select('*');
|
const accountsPromise = this.client.from('user_accounts').select('*');
|
||||||
|
|
||||||
const [
|
const [accountResult, accountsResult] = await Promise.all([
|
||||||
accountResult,
|
|
||||||
accountsResult,
|
|
||||||
{
|
|
||||||
data: { user },
|
|
||||||
},
|
|
||||||
] = await Promise.all([
|
|
||||||
accountPromise,
|
accountPromise,
|
||||||
accountsPromise,
|
accountsPromise,
|
||||||
this.client.auth.getUser(),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (accountResult.error) {
|
if (accountResult.error) {
|
||||||
@@ -104,13 +97,6 @@ export class TeamAccountsApi {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
error: new Error('User is not logged in'),
|
|
||||||
data: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountData = accountResult.data[0];
|
const accountData = accountResult.data[0];
|
||||||
|
|
||||||
if (!accountData) {
|
if (!accountData) {
|
||||||
@@ -124,7 +110,6 @@ export class TeamAccountsApi {
|
|||||||
data: {
|
data: {
|
||||||
account: accountData,
|
account: accountData,
|
||||||
accounts: accountsResult.data,
|
accounts: accountsResult.data,
|
||||||
user,
|
|
||||||
},
|
},
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,10 +24,15 @@ export function useUser(initialData?: User | null) {
|
|||||||
return Promise.reject('Unexpected result format');
|
return Promise.reject('Unexpected result format');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// update staleTime to 5 minutes
|
||||||
|
const staleTime = 1000 * 60 * 5;
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryFn,
|
queryFn,
|
||||||
queryKey,
|
queryKey,
|
||||||
initialData,
|
initialData,
|
||||||
|
staleTime,
|
||||||
|
refetchInterval: false,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user