diff --git a/apps/web/app/home/(user)/_components/home-account-selector.tsx b/apps/web/app/home/(user)/_components/home-account-selector.tsx index 2cd241d66..de30212c0 100644 --- a/apps/web/app/home/(user)/_components/home-account-selector.tsx +++ b/apps/web/app/home/(user)/_components/home-account-selector.tsx @@ -18,6 +18,7 @@ export function HomeAccountSelector(props: { image: string | null; }>; + userId: string; collapsed: boolean; }) { const router = useRouter(); @@ -27,6 +28,7 @@ export function HomeAccountSelector(props: { collapsed={props.collapsed} accounts={props.accounts} features={features} + userId={props.userId} onAccountChange={(value) => { if (value) { const path = pathsConfig.app.accountHome.replace('[account]', value); diff --git a/apps/web/app/home/(user)/_components/home-menu-navigation.tsx b/apps/web/app/home/(user)/_components/home-menu-navigation.tsx index 7ca4ff888..1a3ee3fc8 100644 --- a/apps/web/app/home/(user)/_components/home-menu-navigation.tsx +++ b/apps/web/app/home/(user)/_components/home-menu-navigation.tsx @@ -12,7 +12,7 @@ import { personalAccountNavigationConfig } from '~/config/personal-account-navig // home imports import { HomeAccountSelector } from '../_components/home-account-selector'; 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 }) { const { workspace, user, accounts } = props.workspace; @@ -50,7 +50,11 @@ export function HomeMenuNavigation(props: { workspace: UserWorkspace }) {
- + diff --git a/apps/web/app/home/(user)/_components/home-mobile-navigation.tsx b/apps/web/app/home/(user)/_components/home-mobile-navigation.tsx index cd0236d9f..173f1c0ca 100644 --- a/apps/web/app/home/(user)/_components/home-mobile-navigation.tsx +++ b/apps/web/app/home/(user)/_components/home-mobile-navigation.tsx @@ -22,7 +22,7 @@ import { personalAccountNavigationConfig } from '~/config/personal-account-navig // home imports 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 }) { const signOut = useSignOut(); @@ -69,6 +69,7 @@ export function HomeMobileNavigation(props: { workspace: UserWorkspace }) { diff --git a/apps/web/app/home/(user)/_components/home-sidebar.tsx b/apps/web/app/home/(user)/_components/home-sidebar.tsx index 2904863c8..de11c009c 100644 --- a/apps/web/app/home/(user)/_components/home-sidebar.tsx +++ b/apps/web/app/home/(user)/_components/home-sidebar.tsx @@ -8,7 +8,7 @@ import { personalAccountNavigationConfig } from '~/config/personal-account-navig import { UserNotifications } from '~/home/(user)/_components/user-notifications'; // 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'; export function HomeSidebar(props: { workspace: UserWorkspace }) { @@ -22,7 +22,11 @@ export function HomeSidebar(props: { workspace: UserWorkspace }) { condition={featuresFlagConfig.enableTeamAccounts} fallback={} > - + diff --git a/apps/web/app/home/(user)/_lib/server/load-user-workspace.ts b/apps/web/app/home/(user)/_lib/server/user-workspace.loader.ts similarity index 79% rename from apps/web/app/home/(user)/_lib/server/load-user-workspace.ts rename to apps/web/app/home/(user)/_lib/server/user-workspace.loader.ts index a4cff39b1..d86b17356 100644 --- a/apps/web/app/home/(user)/_lib/server/load-user-workspace.ts +++ b/apps/web/app/home/(user)/_lib/server/user-workspace.loader.ts @@ -1,6 +1,9 @@ import { cache } from 'react'; +import { redirect } from 'next/navigation'; + import { createAccountsApi } from '@kit/accounts/api'; +import { requireUser } from '@kit/supabase/require-user'; import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client'; import featureFlagsConfig from '~/config/feature-flags.config'; @@ -24,20 +27,19 @@ export const loadUserWorkspace = cache(async () => { : () => Promise.resolve([]); const workspacePromise = api.getAccountWorkspace(); - const userPromise = client.auth.getUser(); - const [accounts, workspace, userResult] = await Promise.all([ + const [accounts, workspace, auth] = await Promise.all([ accountsPromise(), workspacePromise, - userPromise, + requireUser(client), ]); - const user = userResult.data.user; - - if (!user) { - throw new Error('User is not logged in'); + if (!auth.data) { + return redirect(auth.redirectTo); } + const user = auth.data; + return { accounts, workspace, diff --git a/apps/web/app/home/(user)/layout.tsx b/apps/web/app/home/(user)/layout.tsx index 228ea90c2..649565f65 100644 --- a/apps/web/app/home/(user)/layout.tsx +++ b/apps/web/app/home/(user)/layout.tsx @@ -18,7 +18,7 @@ import { withI18n } from '~/lib/i18n/with-i18n'; import { HomeMenuNavigation } from './_components/home-menu-navigation'; import { HomeMobileNavigation } from './_components/home-mobile-navigation'; 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) { const workspace = use(loadUserWorkspace()); diff --git a/apps/web/app/home/(user)/settings/page.tsx b/apps/web/app/home/(user)/settings/page.tsx index 396242625..17e018a48 100644 --- a/apps/web/app/home/(user)/settings/page.tsx +++ b/apps/web/app/home/(user)/settings/page.tsx @@ -1,8 +1,11 @@ +import { use } from 'react'; + import { PersonalAccountSettingsContainer } from '@kit/accounts/personal-account-settings'; import { PageBody } from '@kit/ui/page'; import featureFlagsConfig from '~/config/feature-flags.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 { withI18n } from '~/lib/i18n/with-i18n'; @@ -24,10 +27,16 @@ export const generateMetadata = async () => { }; function PersonalAccountSettingsPage() { + const { user } = use(loadUserWorkspace()); + return (
- +
); diff --git a/apps/web/app/home/[account]/_components/team-account-accounts-selector.tsx b/apps/web/app/home/[account]/_components/team-account-accounts-selector.tsx index ec520ba4a..72a64264e 100644 --- a/apps/web/app/home/[account]/_components/team-account-accounts-selector.tsx +++ b/apps/web/app/home/[account]/_components/team-account-accounts-selector.tsx @@ -13,6 +13,8 @@ const features = { export function TeamAccountAccountsSelector(params: { selectedAccount: string; + userId: string; + accounts: Array<{ label: string | null; value: string | null; @@ -25,6 +27,7 @@ export function TeamAccountAccountsSelector(params: { { diff --git a/apps/web/app/home/[account]/_components/team-account-layout-mobile-navigation.tsx b/apps/web/app/home/[account]/_components/team-account-layout-mobile-navigation.tsx index 9c53f6052..a8df0a8c4 100644 --- a/apps/web/app/home/[account]/_components/team-account-layout-mobile-navigation.tsx +++ b/apps/web/app/home/[account]/_components/team-account-layout-mobile-navigation.tsx @@ -41,6 +41,7 @@ const features = { export const TeamAccountLayoutMobileNavigation = ( props: React.PropsWithChildren<{ account: string; + userId: string; accounts: Accounts; }>, ) => { @@ -83,7 +84,7 @@ export const TeamAccountLayoutMobileNavigation = ( - + {Links} @@ -137,7 +138,7 @@ function SignOutDropdownItem( ); } -function TeamAccountsModal(props: { accounts: Accounts }) { +function TeamAccountsModal(props: { accounts: Accounts; userId: string }) { const router = useRouter(); return ( @@ -165,6 +166,7 @@ function TeamAccountsModal(props: { accounts: Accounts }) {
{ const path = value ? pathsConfig.app.accountHome.replace('[account]', value) diff --git a/apps/web/app/home/[account]/_components/team-account-layout-sidebar.tsx b/apps/web/app/home/[account]/_components/team-account-layout-sidebar.tsx index c694a9899..2df34e41a 100644 --- a/apps/web/app/home/[account]/_components/team-account-layout-sidebar.tsx +++ b/apps/web/app/home/[account]/_components/team-account-layout-sidebar.tsx @@ -59,7 +59,8 @@ function SidebarContainer(props: { collapsible?: boolean; user: User; }) { - const { account, accounts } = props; + const { account, accounts, user } = props; + const userId = user.id; return ( <> @@ -68,12 +69,13 @@ function SidebarContainer(props: { className={'flex max-w-full items-center justify-between space-x-4'} >
diff --git a/apps/web/app/home/[account]/_components/team-account-navigation-menu.tsx b/apps/web/app/home/[account]/_components/team-account-navigation-menu.tsx index feb320c4c..199b164ee 100644 --- a/apps/web/app/home/[account]/_components/team-account-navigation-menu.tsx +++ b/apps/web/app/home/[account]/_components/team-account-navigation-menu.tsx @@ -50,6 +50,7 @@ export function TeamAccountNavigationMenu(props: {
({ label: account.name, diff --git a/apps/web/app/home/[account]/_lib/server/team-account-workspace.loader.ts b/apps/web/app/home/[account]/_lib/server/team-account-workspace.loader.ts index d273d04de..f41ccf31b 100644 --- a/apps/web/app/home/[account]/_lib/server/team-account-workspace.loader.ts +++ b/apps/web/app/home/[account]/_lib/server/team-account-workspace.loader.ts @@ -4,6 +4,7 @@ import { cache } from 'react'; import { redirect } from 'next/navigation'; +import { requireUser } from '@kit/supabase/require-user'; import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client'; import { createTeamAccountsApi } from '@kit/team-accounts/api'; @@ -26,19 +27,25 @@ export const loadTeamWorkspace = cache(async (accountSlug: string) => { const client = getSupabaseServerComponentClient(); const api = createTeamAccountsApi(client); - const workspace = await api.getAccountWorkspace(accountSlug); - - if (workspace.error) { - throw workspace.error; - } - - const account = workspace.data.account; + const [workspace, auth] = await Promise.all([ + api.getAccountWorkspace(accountSlug), + requireUser(client), + ]); // we cannot find any record for the selected account // so we redirect the user to the home page - if (!account) { + if (!workspace.data?.account) { return redirect(pathsConfig.app.home); } - return workspace.data; + if (!auth.data) { + return redirect(auth.redirectTo); + } + + const user = auth.data; + + return { + ...workspace.data, + user, + }; }); diff --git a/apps/web/app/home/[account]/layout.tsx b/apps/web/app/home/[account]/layout.tsx index 55c9c36e4..7be08f0ad 100644 --- a/apps/web/app/home/[account]/layout.tsx +++ b/apps/web/app/home/[account]/layout.tsx @@ -62,6 +62,7 @@ function TeamWorkspaceLayout({
diff --git a/apps/web/app/home/[account]/members/_lib/server/members-page.loader.ts b/apps/web/app/home/[account]/members/_lib/server/members-page.loader.ts index f9f471956..76d415c8c 100644 --- a/apps/web/app/home/[account]/members/_lib/server/members-page.loader.ts +++ b/apps/web/app/home/[account]/members/_lib/server/members-page.loader.ts @@ -19,7 +19,6 @@ export async function loadMembersPageData( loadTeamWorkspace(slug), loadAccountMembers(client, slug), loadInvitations(client, slug), - loadUser(client), canAddMember, ]); } @@ -38,16 +37,6 @@ async function canAddMember() { return Promise.resolve(true); } -async function loadUser(client: SupabaseClient) { - const { data, error } = await client.auth.getUser(); - - if (error) { - throw error; - } - - return data.user; -} - /** * Load account members * @param client diff --git a/apps/web/app/home/[account]/members/page.tsx b/apps/web/app/home/[account]/members/page.tsx index 4a51b92b3..7f76e45b3 100644 --- a/apps/web/app/home/[account]/members/page.tsx +++ b/apps/web/app/home/[account]/members/page.tsx @@ -18,6 +18,7 @@ import { If } from '@kit/ui/if'; import { PageBody } from '@kit/ui/page'; 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 { withI18n } from '~/lib/i18n/with-i18n'; @@ -43,7 +44,9 @@ export const generateMetadata = async () => { async function TeamAccountMembersPage({ params }: Params) { 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); const canManageRoles = account.permissions.includes('roles.manage'); diff --git a/apps/web/components/personal-account-dropdown-container.tsx b/apps/web/components/personal-account-dropdown-container.tsx index 4bb727804..407e7dcc8 100644 --- a/apps/web/components/personal-account-dropdown-container.tsx +++ b/apps/web/components/personal-account-dropdown-container.tsx @@ -19,7 +19,7 @@ const features = { export function ProfileAccountDropdownContainer(props: { collapsed: boolean; - user: User | null; + user: User; account?: { id: string | null; @@ -29,7 +29,7 @@ export function ProfileAccountDropdownContainer(props: { }) { const signOut = useSignOut(); const user = useUser(props.user); - const userData = user.data ?? props.user ?? null; + const userData = user.data as User; return (
diff --git a/apps/web/lib/root-metdata.ts b/apps/web/lib/root-metdata.ts index d8aea6fcc..d5c41a15b 100644 --- a/apps/web/lib/root-metdata.ts +++ b/apps/web/lib/root-metdata.ts @@ -22,11 +22,6 @@ export const rootMetadata: Metadata = { }, icons: { icon: '/images/favicon/favicon.ico', - shortcut: '/shortcut-icon.png', apple: '/images/favicon/apple-touch-icon.png', - other: { - rel: 'apple-touch-icon-precomposed', - url: '/apple-touch-icon-precomposed.png', - }, }, }; diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index ed1d784d2..ef9d74cbf 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -1,8 +1,6 @@ import type { NextRequest } from 'next/server'; import { NextResponse, URLPattern } from 'next/server'; -import type { UserResponse } from '@supabase/supabase-js'; - import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs'; import { checkRequiresMultiFactorAuthentication } from '@kit/supabase/check-requires-mfa'; @@ -15,17 +13,17 @@ const CSRF_SECRET_COOKIE = 'csrfSecret'; const NEXT_ACTION_HEADER = 'next-action'; export const config = { - matcher: [ - '/((?!_next/static|_next/image|favicon.ico|locales|assets|api/*).*)', - ], + matcher: ['/((?!_next/static|_next/image|images|locales|assets|api/*).*)'], +}; + +const getUser = (request: NextRequest, response: NextResponse) => { + const supabase = createMiddlewareClient(request, response); + + return supabase.auth.getUser(); }; export async function middleware(request: NextRequest) { 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 // 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 (handlePattern) { - const patternHandlerResponse = await handlePattern( - request, - csrfResponse, - userResponse, - ); + const patternHandlerResponse = await handlePattern(request, csrfResponse); // if a pattern handler returns a response, return it if (patternHandlerResponse) { @@ -95,18 +89,14 @@ function isServerAction(request: NextRequest) { return headers.has(NEXT_ACTION_HEADER); } -function adminMiddleware( - request: NextRequest, - response: NextResponse, - userResponse: UserResponse, -) { +async function adminMiddleware(request: NextRequest, response: NextResponse) { const isAdminPath = request.nextUrl.pathname.startsWith('/admin'); if (!isAdminPath) { return response; } - const { data, error } = userResponse; + const { data, error } = await getUser(request, response); // If user is not logged in, redirect to sign in page. // This should never happen, but just in case. @@ -138,12 +128,8 @@ function getPatterns() { }, { pattern: new URLPattern({ pathname: '/auth*' }), - handler: ( - req: NextRequest, - _: NextResponse, - userResponse: UserResponse, - ) => { - const user = userResponse.data.user; + handler: async (req: NextRequest, res: NextResponse) => { + const { data: user } = await getUser(req, res); // the user is logged out, so we don't need to do anything if (!user) { @@ -164,14 +150,8 @@ function getPatterns() { }, { pattern: new URLPattern({ pathname: '/home*' }), - handler: async ( - req: NextRequest, - res: NextResponse, - userResponse: UserResponse, - ) => { - const { - data: { user }, - } = userResponse; + handler: async (req: NextRequest, res: NextResponse) => { + const { data: user } = await getUser(req, res); const origin = req.nextUrl.origin; const next = req.nextUrl.pathname; diff --git a/packages/features/accounts/src/components/account-selector.tsx b/packages/features/accounts/src/components/account-selector.tsx index 81f8426b3..309031cea 100644 --- a/packages/features/accounts/src/components/account-selector.tsx +++ b/packages/features/accounts/src/components/account-selector.tsx @@ -36,6 +36,7 @@ interface AccountSelectorProps { enableTeamCreation: boolean; }; + userId: string; selectedAccount?: string; collapsed?: boolean; className?: string; @@ -49,6 +50,7 @@ export function AccountSelector({ accounts, selectedAccount, onAccountChange, + userId, className, features = { enableTeamCreation: true, @@ -63,7 +65,7 @@ export function AccountSelector({ ); const { t } = useTranslation('teams'); - const personalData = usePersonalAccountData(); + const personalData = usePersonalAccountData(userId); useEffect(() => { setValue(selectedAccount ?? PERSONAL_ACCOUNT_SLUG); diff --git a/packages/features/accounts/src/components/personal-account-dropdown.tsx b/packages/features/accounts/src/components/personal-account-dropdown.tsx index 38f1a1b42..f066e4789 100644 --- a/packages/features/accounts/src/components/personal-account-dropdown.tsx +++ b/packages/features/accounts/src/components/personal-account-dropdown.tsx @@ -38,8 +38,7 @@ export function PersonalAccountDropdown({ features, account, }: { - className?: string; - user: User | null; + user: User; account?: { id: string | null; @@ -48,7 +47,6 @@ export function PersonalAccountDropdown({ }; signOutRequested: () => unknown; - showProfileName?: boolean; paths: { home: string; @@ -57,8 +55,14 @@ export function PersonalAccountDropdown({ features: { enableThemeToggle: boolean; }; + + className?: string; + showProfileName?: boolean; }) { - const { data: personalAccountData } = usePersonalAccountData(account); + const { data: personalAccountData } = usePersonalAccountData( + user.id, + account, + ); const signedInAsLabel = useMemo(() => { const email = user?.email ?? undefined; diff --git a/packages/features/accounts/src/components/personal-account-settings/account-settings-container.tsx b/packages/features/accounts/src/components/personal-account-settings/account-settings-container.tsx index 633d2460b..37f3325ff 100644 --- a/packages/features/accounts/src/components/personal-account-settings/account-settings-container.tsx +++ b/packages/features/accounts/src/components/personal-account-settings/account-settings-container.tsx @@ -24,6 +24,8 @@ import { UpdateAccountImageContainer } from './update-account-image-container'; export function PersonalAccountSettingsContainer( props: React.PropsWithChildren<{ + userId: string; + features: { enableAccountDeletion: boolean; }; @@ -34,7 +36,7 @@ export function PersonalAccountSettingsContainer( }>, ) { const supportsLanguageSelection = useSupportMultiLanguage(); - const user = usePersonalAccountData(); + const user = usePersonalAccountData(props.userId); if (!user.data || user.isPending) { return ; diff --git a/packages/features/accounts/src/hooks/use-personal-account-data.ts b/packages/features/accounts/src/hooks/use-personal-account-data.ts index e2a48effb..15d96fa49 100644 --- a/packages/features/accounts/src/hooks/use-personal-account-data.ts +++ b/packages/features/accounts/src/hooks/use-personal-account-data.ts @@ -3,9 +3,9 @@ import { useCallback } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useSupabase } from '@kit/supabase/hooks/use-supabase'; -import { useUser } from '@kit/supabase/hooks/use-user'; export function usePersonalAccountData( + userId: string, partialAccount?: | { id: string | null; @@ -15,9 +15,6 @@ export function usePersonalAccountData( | undefined, ) { const client = useSupabase(); - const user = useUser(); - const userId = user.data?.id; - const queryKey = ['account:data', userId]; const queryFn = async () => { diff --git a/packages/features/team-accounts/src/server/api.ts b/packages/features/team-accounts/src/server/api.ts index 2fb4e16c2..1fdaa0bee 100644 --- a/packages/features/team-accounts/src/server/api.ts +++ b/packages/features/team-accounts/src/server/api.ts @@ -78,16 +78,9 @@ export class TeamAccountsApi { const accountsPromise = this.client.from('user_accounts').select('*'); - const [ - accountResult, - accountsResult, - { - data: { user }, - }, - ] = await Promise.all([ + const [accountResult, accountsResult] = await Promise.all([ accountPromise, accountsPromise, - this.client.auth.getUser(), ]); 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]; if (!accountData) { @@ -124,7 +110,6 @@ export class TeamAccountsApi { data: { account: accountData, accounts: accountsResult.data, - user, }, error: null, }; diff --git a/packages/supabase/src/hooks/use-user.ts b/packages/supabase/src/hooks/use-user.ts index 0972999e2..198005b38 100644 --- a/packages/supabase/src/hooks/use-user.ts +++ b/packages/supabase/src/hooks/use-user.ts @@ -24,10 +24,15 @@ export function useUser(initialData?: User | null) { return Promise.reject('Unexpected result format'); }; + // update staleTime to 5 minutes + const staleTime = 1000 * 60 * 5; + return useQuery({ queryFn, queryKey, initialData, + staleTime, + refetchInterval: false, refetchOnMount: false, refetchOnWindowFocus: false, });