chore: bump version to 3.1.1 in package.json; refactor mobile navigation components and improve layout structure (#472)
- Updated version to 3.1.1 in package.json. - Refactored mobile navigation components to enhance structure and usability. - Adjusted layout components to improve responsiveness and visual consistency. - Introduced shared mobile navigation components for better code reuse.
This commit is contained in:
committed by
GitHub
parent
6268d1bab0
commit
c837d4f592
@@ -22,13 +22,15 @@ export function HomeAccountSelector(props: {
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
userId: string;
|
userId: string;
|
||||||
|
collapsed?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const context = useContext(SidebarContext);
|
const context = useContext(SidebarContext);
|
||||||
|
const collapsed = props.collapsed ?? !context?.open;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountSelector
|
<AccountSelector
|
||||||
collapsed={!context?.open}
|
collapsed={collapsed}
|
||||||
accounts={props.accounts}
|
accounts={props.accounts}
|
||||||
features={features}
|
features={features}
|
||||||
userId={props.userId}
|
userId={props.userId}
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ export function HomeMenuNavigation(props: { workspace: UserWorkspace }) {
|
|||||||
return (
|
return (
|
||||||
<div className={'flex w-full flex-1 justify-between'}>
|
<div className={'flex w-full flex-1 justify-between'}>
|
||||||
<div className={'flex items-center space-x-8'}>
|
<div className={'flex items-center space-x-8'}>
|
||||||
|
<div>
|
||||||
<AppLogo />
|
<AppLogo />
|
||||||
|
</div>
|
||||||
|
|
||||||
<BorderedNavigationMenu>
|
<BorderedNavigationMenu>
|
||||||
{routes.map((route) => (
|
{routes.map((route) => (
|
||||||
@@ -54,7 +56,9 @@ export function HomeMenuNavigation(props: { workspace: UserWorkspace }) {
|
|||||||
</If>
|
</If>
|
||||||
|
|
||||||
<If condition={featuresFlagConfig.enableTeamAccounts}>
|
<If condition={featuresFlagConfig.enableTeamAccounts}>
|
||||||
|
<div>
|
||||||
<HomeAccountSelector userId={user.id} accounts={accounts} />
|
<HomeAccountSelector userId={user.id} accounts={accounts} />
|
||||||
|
</div>
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import { Menu } from 'lucide-react';
|
||||||
|
|
||||||
import { LogOut, Menu } from 'lucide-react';
|
|
||||||
|
|
||||||
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
|
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuGroup,
|
DropdownMenuGroup,
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
@@ -17,6 +14,10 @@ import {
|
|||||||
import { If } from '@kit/ui/if';
|
import { If } from '@kit/ui/if';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MobileNavRouteLinks,
|
||||||
|
MobileNavSignOutItem,
|
||||||
|
} from '~/components/mobile-navigation-shared';
|
||||||
import featuresFlagConfig from '~/config/feature-flags.config';
|
import featuresFlagConfig from '~/config/feature-flags.config';
|
||||||
import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
|
import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
|
||||||
|
|
||||||
@@ -27,25 +28,6 @@ import type { UserWorkspace } from '../_lib/server/load-user-workspace';
|
|||||||
export function HomeMobileNavigation(props: { workspace: UserWorkspace }) {
|
export function HomeMobileNavigation(props: { workspace: UserWorkspace }) {
|
||||||
const signOut = useSignOut();
|
const signOut = useSignOut();
|
||||||
|
|
||||||
const Links = personalAccountNavigationConfig.routes.map((item, index) => {
|
|
||||||
if ('children' in item) {
|
|
||||||
return item.children.map((child) => {
|
|
||||||
return (
|
|
||||||
<DropdownLink
|
|
||||||
key={child.path}
|
|
||||||
Icon={child.Icon}
|
|
||||||
path={child.path}
|
|
||||||
label={child.label}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('divider' in item) {
|
|
||||||
return <DropdownMenuSeparator key={index} />;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
@@ -60,6 +42,7 @@ export function HomeMobileNavigation(props: { workspace: UserWorkspace }) {
|
|||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
|
|
||||||
<HomeAccountSelector
|
<HomeAccountSelector
|
||||||
|
collapsed={false}
|
||||||
userId={props.workspace.user.id}
|
userId={props.workspace.user.id}
|
||||||
accounts={props.workspace.accounts}
|
accounts={props.workspace.accounts}
|
||||||
/>
|
/>
|
||||||
@@ -68,57 +51,16 @@ export function HomeMobileNavigation(props: { workspace: UserWorkspace }) {
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
<DropdownMenuGroup>{Links}</DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
|
<MobileNavRouteLinks
|
||||||
|
routes={personalAccountNavigationConfig.routes}
|
||||||
|
/>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
<SignOutDropdownItem onSignOut={() => signOut.mutateAsync()} />
|
<MobileNavSignOutItem onSignOut={() => signOut.mutateAsync()} />
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownLink(
|
|
||||||
props: React.PropsWithChildren<{
|
|
||||||
path: string;
|
|
||||||
label: string;
|
|
||||||
Icon: React.ReactNode;
|
|
||||||
}>,
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuItem
|
|
||||||
render={
|
|
||||||
<Link
|
|
||||||
href={props.path}
|
|
||||||
className={'flex h-12 w-full items-center space-x-4'}
|
|
||||||
>
|
|
||||||
{props.Icon}
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<Trans i18nKey={props.label} defaults={props.label} />
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
}
|
|
||||||
key={props.path}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SignOutDropdownItem(
|
|
||||||
props: React.PropsWithChildren<{
|
|
||||||
onSignOut: () => unknown;
|
|
||||||
}>,
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuItem
|
|
||||||
className={'flex h-12 w-full items-center space-x-4'}
|
|
||||||
onClick={props.onSignOut}
|
|
||||||
>
|
|
||||||
<LogOut className={'h-6'} />
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<Trans i18nKey={'common.signOut'} defaults={'Sign out'} />
|
|
||||||
</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
|
import { cookies } from 'next/headers';
|
||||||
|
|
||||||
import { PageHeader } from '@kit/ui/page';
|
import { PageHeader } from '@kit/ui/page';
|
||||||
|
|
||||||
export function HomeLayoutPageHeader(
|
import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
|
||||||
|
|
||||||
|
export async function HomeLayoutPageHeader(
|
||||||
props: React.PropsWithChildren<{
|
props: React.PropsWithChildren<{
|
||||||
title: string | React.ReactNode;
|
title: string | React.ReactNode;
|
||||||
description: string | React.ReactNode;
|
description: string | React.ReactNode;
|
||||||
}>,
|
}>,
|
||||||
) {
|
) {
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
const layoutStyleCookie = cookieStore.get('layout-style')?.value;
|
||||||
|
const displaySidebarTrigger =
|
||||||
|
(layoutStyleCookie ?? personalAccountNavigationConfig.style) === 'sidebar';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageHeader description={props.description}>{props.children}</PageHeader>
|
<PageHeader
|
||||||
|
description={props.description}
|
||||||
|
displaySidebarTrigger={displaySidebarTrigger}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</PageHeader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class UserBillingService {
|
|||||||
planId,
|
planId,
|
||||||
customerId,
|
customerId,
|
||||||
accountId,
|
accountId,
|
||||||
error: message
|
error: message,
|
||||||
},
|
},
|
||||||
`Checkout session not created due to an error`,
|
`Checkout session not created due to an error`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ async function SidebarLayout({ children }: React.PropsWithChildren) {
|
|||||||
<HomeSidebar workspace={workspace} />
|
<HomeSidebar workspace={workspace} />
|
||||||
</PageNavigation>
|
</PageNavigation>
|
||||||
|
|
||||||
<PageMobileNavigation className={'flex items-center justify-between'}>
|
<PageMobileNavigation>
|
||||||
<MobileNavigation workspace={workspace} />
|
<MobileNavigation workspace={workspace} />
|
||||||
</PageMobileNavigation>
|
</PageMobileNavigation>
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ async function HeaderLayout({ children }: React.PropsWithChildren) {
|
|||||||
<HomeMenuNavigation workspace={workspace} />
|
<HomeMenuNavigation workspace={workspace} />
|
||||||
</PageNavigation>
|
</PageNavigation>
|
||||||
|
|
||||||
<PageMobileNavigation className={'flex items-center justify-between'}>
|
<PageMobileNavigation>
|
||||||
<MobileNavigation workspace={workspace} />
|
<MobileNavigation workspace={workspace} />
|
||||||
</PageMobileNavigation>
|
</PageMobileNavigation>
|
||||||
|
|
||||||
@@ -92,7 +92,9 @@ function MobileNavigation({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div>
|
||||||
<AppLogo />
|
<AppLogo />
|
||||||
|
</div>
|
||||||
|
|
||||||
<HomeMobileNavigation workspace={workspace} />
|
<HomeMobileNavigation workspace={workspace} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import { AccountSelector } from '@kit/accounts/account-selector';
|
import { AccountSelector } from '@kit/accounts/account-selector';
|
||||||
import { useSidebar } from '@kit/ui/sidebar';
|
import { SidebarContext } from '@kit/ui/sidebar';
|
||||||
|
|
||||||
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';
|
||||||
@@ -23,7 +25,7 @@ export function TeamAccountAccountsSelector(params: {
|
|||||||
}>;
|
}>;
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const ctx = useSidebar();
|
const ctx = useContext(SidebarContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountSelector
|
<AccountSelector
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import { Home, LogOut, Menu } from 'lucide-react';
|
import { Menu } from 'lucide-react';
|
||||||
|
|
||||||
import { AccountSelector } from '@kit/accounts/account-selector';
|
import { AccountSelector } from '@kit/accounts/account-selector';
|
||||||
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
|
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from '@kit/ui/dialog';
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@kit/ui/dropdown-menu';
|
} from '@kit/ui/dropdown-menu';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MobileNavRouteLinks,
|
||||||
|
MobileNavSignOutItem,
|
||||||
|
} from '~/components/mobile-navigation-shared';
|
||||||
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 { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config';
|
import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config';
|
||||||
@@ -34,7 +31,6 @@ type Accounts = Array<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
const features = {
|
const features = {
|
||||||
enableTeamAccounts: featureFlagsConfig.enableTeamAccounts,
|
|
||||||
enableTeamCreation: featureFlagsConfig.enableTeamCreation,
|
enableTeamCreation: featureFlagsConfig.enableTeamCreation,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,128 +41,21 @@ export const TeamAccountLayoutMobileNavigation = (
|
|||||||
accounts: Accounts;
|
accounts: Accounts;
|
||||||
}>,
|
}>,
|
||||||
) => {
|
) => {
|
||||||
|
const router = useRouter();
|
||||||
const signOut = useSignOut();
|
const signOut = useSignOut();
|
||||||
|
|
||||||
const Links = getTeamAccountSidebarConfig(props.account).routes.map(
|
|
||||||
(item, index) => {
|
|
||||||
if ('children' in item) {
|
|
||||||
return item.children.map((child) => {
|
|
||||||
return (
|
|
||||||
<DropdownLink
|
|
||||||
key={child.path}
|
|
||||||
Icon={child.Icon}
|
|
||||||
path={child.path}
|
|
||||||
label={child.label}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('divider' in item) {
|
|
||||||
return <DropdownMenuSeparator key={index} />;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<Menu className={'h-9'} />
|
<Menu className={'h-9'} />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
<DropdownMenuContent sideOffset={10} className={'w-screen rounded-none'}>
|
<DropdownMenuContent className={'w-screen rounded-none'}>
|
||||||
<TeamAccountsModal
|
<DropdownMenuGroup>
|
||||||
userId={props.userId}
|
<DropdownMenuLabel>
|
||||||
accounts={props.accounts}
|
|
||||||
account={props.account}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{Links}
|
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
|
|
||||||
<SignOutDropdownItem onSignOut={() => signOut.mutateAsync()} />
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function DropdownLink(
|
|
||||||
props: React.PropsWithChildren<{
|
|
||||||
path: string;
|
|
||||||
label: string;
|
|
||||||
Icon: React.ReactNode;
|
|
||||||
}>,
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuItem
|
|
||||||
render={
|
|
||||||
<Link
|
|
||||||
href={props.path}
|
|
||||||
className={'flex h-12 w-full items-center gap-x-3 px-3'}
|
|
||||||
>
|
|
||||||
{props.Icon}
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<Trans i18nKey={props.label} defaults={props.label} />
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SignOutDropdownItem(
|
|
||||||
props: React.PropsWithChildren<{
|
|
||||||
onSignOut: () => unknown;
|
|
||||||
}>,
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuItem
|
|
||||||
className={'flex h-12 w-full items-center space-x-2'}
|
|
||||||
onClick={props.onSignOut}
|
|
||||||
>
|
|
||||||
<LogOut className={'h-4'} />
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<Trans i18nKey={'common.signOut'} />
|
|
||||||
</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function TeamAccountsModal(props: {
|
|
||||||
accounts: Accounts;
|
|
||||||
userId: string;
|
|
||||||
account: string;
|
|
||||||
}) {
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger
|
|
||||||
render={
|
|
||||||
<DropdownMenuItem
|
|
||||||
className={'flex h-12 w-full items-center space-x-2'}
|
|
||||||
onSelect={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<Home className={'h-4'} />
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<Trans i18nKey={'common.yourAccounts'} />
|
<Trans i18nKey={'common.yourAccounts'} />
|
||||||
</span>
|
</DropdownMenuLabel>
|
||||||
</DropdownMenuItem>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>
|
|
||||||
<Trans i18nKey={'common.yourAccounts'} />
|
|
||||||
</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<div className={'py-6'}>
|
|
||||||
<AccountSelector
|
<AccountSelector
|
||||||
className={'w-full max-w-full'}
|
className={'w-full max-w-full'}
|
||||||
userId={props.userId}
|
userId={props.userId}
|
||||||
@@ -185,8 +74,20 @@ function TeamAccountsModal(props: {
|
|||||||
router.replace(path);
|
router.replace(path);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</DropdownMenuGroup>
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<MobileNavRouteLinks
|
||||||
|
routes={getTeamAccountSidebarConfig(props.account).routes}
|
||||||
|
/>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
|
<MobileNavSignOutItem onSignOut={() => signOut.mutateAsync()} />
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,13 +1,28 @@
|
|||||||
|
import { cookies } from 'next/headers';
|
||||||
|
|
||||||
import { PageHeader } from '@kit/ui/page';
|
import { PageHeader } from '@kit/ui/page';
|
||||||
|
|
||||||
export function TeamAccountLayoutPageHeader(
|
import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config';
|
||||||
|
|
||||||
|
export async function TeamAccountLayoutPageHeader(
|
||||||
props: React.PropsWithChildren<{
|
props: React.PropsWithChildren<{
|
||||||
title: string | React.ReactNode;
|
title: string | React.ReactNode;
|
||||||
description: string | React.ReactNode;
|
description: string | React.ReactNode;
|
||||||
account: string;
|
account: string;
|
||||||
}>,
|
}>,
|
||||||
) {
|
) {
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
const layoutStyleCookie = cookieStore.get('layout-style')?.value;
|
||||||
|
const defaultStyle = getTeamAccountSidebarConfig(props.account).style;
|
||||||
|
const displaySidebarTrigger =
|
||||||
|
(layoutStyleCookie ?? defaultStyle) === 'sidebar';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageHeader description={props.description}>{props.children}</PageHeader>
|
<PageHeader
|
||||||
|
description={props.description}
|
||||||
|
displaySidebarTrigger={displaySidebarTrigger}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</PageHeader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ export function TeamAccountNavigationMenu(props: {
|
|||||||
return (
|
return (
|
||||||
<div className={'flex w-full flex-1 justify-between'}>
|
<div className={'flex w-full flex-1 justify-between'}>
|
||||||
<div className={'flex items-center space-x-8'}>
|
<div className={'flex items-center space-x-8'}>
|
||||||
|
<div>
|
||||||
<AppLogo />
|
<AppLogo />
|
||||||
|
</div>
|
||||||
|
|
||||||
<BorderedNavigationMenu>
|
<BorderedNavigationMenu>
|
||||||
{routes.map((route) => (
|
{routes.map((route) => (
|
||||||
@@ -50,11 +52,12 @@ export function TeamAccountNavigationMenu(props: {
|
|||||||
</BorderedNavigationMenu>
|
</BorderedNavigationMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={'flex items-center justify-end space-x-2.5'}>
|
<div className={'flex items-center justify-end space-x-1'}>
|
||||||
<If condition={featureFlagsConfig.enableNotifications}>
|
<If condition={featureFlagsConfig.enableNotifications}>
|
||||||
<TeamAccountNotifications accountId={account.id} userId={user.id} />
|
<TeamAccountNotifications accountId={account.id} userId={user.id} />
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
|
<div>
|
||||||
<TeamAccountAccountsSelector
|
<TeamAccountAccountsSelector
|
||||||
userId={user.id}
|
userId={user.id}
|
||||||
selectedAccount={account.slug}
|
selectedAccount={account.slug}
|
||||||
@@ -64,6 +67,7 @@ export function TeamAccountNavigationMenu(props: {
|
|||||||
image: account.picture_url,
|
image: account.picture_url,
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<ProfileAccountDropdownContainer
|
<ProfileAccountDropdownContainer
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ class TeamBillingService {
|
|||||||
logger.error(
|
logger.error(
|
||||||
{
|
{
|
||||||
...ctx,
|
...ctx,
|
||||||
error: message
|
error: message,
|
||||||
},
|
},
|
||||||
`Error creating the checkout session`,
|
`Error creating the checkout session`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -67,10 +67,10 @@ async function SidebarLayout({
|
|||||||
/>
|
/>
|
||||||
</PageNavigation>
|
</PageNavigation>
|
||||||
|
|
||||||
<PageMobileNavigation className={'flex items-center justify-between'}>
|
<PageMobileNavigation>
|
||||||
<AppLogo />
|
<AppLogo />
|
||||||
|
|
||||||
<div className={'flex space-x-4'}>
|
<div className={'flex'}>
|
||||||
<TeamAccountLayoutMobileNavigation
|
<TeamAccountLayoutMobileNavigation
|
||||||
userId={data.user.id}
|
userId={data.user.id}
|
||||||
accounts={accounts}
|
accounts={accounts}
|
||||||
@@ -94,6 +94,12 @@ function HeaderLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
const data = use(loadTeamWorkspace(account));
|
const data = use(loadTeamWorkspace(account));
|
||||||
|
|
||||||
|
const accounts = data.accounts.map(({ name, slug, picture_url }) => ({
|
||||||
|
label: name,
|
||||||
|
value: slug,
|
||||||
|
image: picture_url,
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TeamAccountWorkspaceContextProvider value={data}>
|
<TeamAccountWorkspaceContextProvider value={data}>
|
||||||
<Page style={'header'}>
|
<Page style={'header'}>
|
||||||
@@ -101,6 +107,20 @@ function HeaderLayout({
|
|||||||
<TeamAccountNavigationMenu workspace={data} />
|
<TeamAccountNavigationMenu workspace={data} />
|
||||||
</PageNavigation>
|
</PageNavigation>
|
||||||
|
|
||||||
|
<PageMobileNavigation className={'flex items-center justify-between'}>
|
||||||
|
<div>
|
||||||
|
<AppLogo />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TeamAccountLayoutMobileNavigation
|
||||||
|
userId={data.user.id}
|
||||||
|
accounts={accounts}
|
||||||
|
account={account}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</PageMobileNavigation>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</Page>
|
</Page>
|
||||||
</TeamAccountWorkspaceContextProvider>
|
</TeamAccountWorkspaceContextProvider>
|
||||||
|
|||||||
78
apps/web/components/mobile-navigation-shared.tsx
Normal file
78
apps/web/components/mobile-navigation-shared.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
import { LogOut } from 'lucide-react';
|
||||||
|
|
||||||
|
import { DropdownMenuItem, DropdownMenuSeparator } from '@kit/ui/dropdown-menu';
|
||||||
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
|
export function MobileNavDropdownLink(
|
||||||
|
props: React.PropsWithChildren<{
|
||||||
|
path: string;
|
||||||
|
label: string;
|
||||||
|
Icon?: React.ReactNode;
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem
|
||||||
|
render={
|
||||||
|
<Link
|
||||||
|
href={props.path}
|
||||||
|
className={'flex h-12 w-full items-center space-x-4'}
|
||||||
|
>
|
||||||
|
{props.Icon}
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<Trans i18nKey={props.label} defaults={props.label} />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MobileNavSignOutItem(props: { onSignOut: () => unknown }) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={'flex h-12 w-full items-center space-x-4'}
|
||||||
|
onClick={props.onSignOut}
|
||||||
|
>
|
||||||
|
<LogOut className={'h-5'} />
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<Trans i18nKey={'auth.signOut'} defaults={'Sign out'} />
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MobileNavRouteLinks(props: {
|
||||||
|
routes: Array<
|
||||||
|
| {
|
||||||
|
children: Array<{
|
||||||
|
path: string;
|
||||||
|
label: string;
|
||||||
|
Icon?: React.ReactNode;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
| { divider: true }
|
||||||
|
>;
|
||||||
|
}) {
|
||||||
|
return props.routes.map((item, index) => {
|
||||||
|
if ('children' in item) {
|
||||||
|
return item.children.map((child) => (
|
||||||
|
<MobileNavDropdownLink
|
||||||
|
key={child.path}
|
||||||
|
Icon={child.Icon}
|
||||||
|
path={child.path}
|
||||||
|
label={child.label}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('divider' in item) {
|
||||||
|
return <DropdownMenuSeparator key={index} />;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "next-supabase-saas-kit-turbo",
|
"name": "next-supabase-saas-kit-turbo",
|
||||||
"version": "3.1.0",
|
"version": "3.1.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"author": {
|
"author": {
|
||||||
"name": "MakerKit",
|
"name": "MakerKit",
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ const isHostedMode = process.env.STRIPE_UI_MODE === 'hosted_page';
|
|||||||
|
|
||||||
export const StripeClientEnvSchema = z
|
export const StripeClientEnvSchema = z
|
||||||
.object({
|
.object({
|
||||||
publishableKey: isHostedMode
|
publishableKey: isHostedMode ? z.string().optional() : z.string().min(1),
|
||||||
? z.string().optional()
|
|
||||||
: z.string().min(1),
|
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(schema) => {
|
(schema) => {
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import type { Stripe } from "stripe";
|
import type { Stripe } from 'stripe';
|
||||||
import * as z from "zod";
|
import * as z from 'zod';
|
||||||
|
|
||||||
import type { CreateBillingCheckoutSchema } from "@kit/billing/schema";
|
import type { CreateBillingCheckoutSchema } from '@kit/billing/schema';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description If set to true, users can start a trial without entering their credit card details
|
* @description If set to true, users can start a trial without entering their credit card details
|
||||||
*/
|
*/
|
||||||
const enableTrialWithoutCreditCard =
|
const enableTrialWithoutCreditCard =
|
||||||
process.env.STRIPE_ENABLE_TRIAL_WITHOUT_CC === "true";
|
process.env.STRIPE_ENABLE_TRIAL_WITHOUT_CC === 'true';
|
||||||
|
|
||||||
const UI_MODE_VALUES = ["embedded_page", "hosted_page"] as const;
|
const UI_MODE_VALUES = ['embedded_page', 'hosted_page'] as const;
|
||||||
|
|
||||||
const uiMode = z
|
const uiMode = z
|
||||||
.enum(UI_MODE_VALUES)
|
.enum(UI_MODE_VALUES)
|
||||||
.default("embedded_page")
|
.default('embedded_page')
|
||||||
.parse(process.env.STRIPE_UI_MODE);
|
.parse(process.env.STRIPE_UI_MODE);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,9 +37,9 @@ export async function createStripeCheckout(
|
|||||||
|
|
||||||
// docs: https://stripe.com/docs/billing/subscriptions/build-subscription
|
// docs: https://stripe.com/docs/billing/subscriptions/build-subscription
|
||||||
const mode: Stripe.Checkout.SessionCreateParams.Mode =
|
const mode: Stripe.Checkout.SessionCreateParams.Mode =
|
||||||
params.plan.paymentType === "recurring" ? "subscription" : "payment";
|
params.plan.paymentType === 'recurring' ? 'subscription' : 'payment';
|
||||||
|
|
||||||
const isSubscription = mode === "subscription";
|
const isSubscription = mode === 'subscription';
|
||||||
|
|
||||||
let trialDays: number | null | undefined = params.plan.trialDays;
|
let trialDays: number | null | undefined = params.plan.trialDays;
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ export async function createStripeCheckout(
|
|||||||
? {
|
? {
|
||||||
trial_settings: {
|
trial_settings: {
|
||||||
end_behavior: {
|
end_behavior: {
|
||||||
missing_payment_method: "cancel" as const,
|
missing_payment_method: 'cancel' as const,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -89,10 +89,10 @@ export async function createStripeCheckout(
|
|||||||
const customerCreation =
|
const customerCreation =
|
||||||
isSubscription || customer
|
isSubscription || customer
|
||||||
? ({} as Record<string, string>)
|
? ({} as Record<string, string>)
|
||||||
: { customer_creation: "always" };
|
: { customer_creation: 'always' };
|
||||||
|
|
||||||
const lineItems = params.plan.lineItems.map((item) => {
|
const lineItems = params.plan.lineItems.map((item) => {
|
||||||
if (item.type === "metered") {
|
if (item.type === 'metered') {
|
||||||
return {
|
return {
|
||||||
price: item.id,
|
price: item.id,
|
||||||
};
|
};
|
||||||
@@ -114,7 +114,7 @@ export async function createStripeCheckout(
|
|||||||
const paymentCollectionMethod =
|
const paymentCollectionMethod =
|
||||||
enableTrialWithoutCreditCard && params.plan.trialDays
|
enableTrialWithoutCreditCard && params.plan.trialDays
|
||||||
? {
|
? {
|
||||||
payment_method_collection: "if_required" as const,
|
payment_method_collection: 'if_required' as const,
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ function getUrls(params: {
|
|||||||
}) {
|
}) {
|
||||||
const url = `${params.returnUrl}?session_id={CHECKOUT_SESSION_ID}`;
|
const url = `${params.returnUrl}?session_id={CHECKOUT_SESSION_ID}`;
|
||||||
|
|
||||||
if (params.uiMode === "hosted_page") {
|
if (params.uiMode === 'hosted_page') {
|
||||||
return {
|
return {
|
||||||
success_url: url,
|
success_url: url,
|
||||||
cancel_url: params.returnUrl,
|
cancel_url: params.returnUrl,
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export function PageMobileNavigation(
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center border-b px-4 py-2 lg:hidden lg:px-0',
|
'container flex w-full items-center justify-between px-0 py-2 group-data-[slot="sidebar-wrapper"]/sidebar-wrapper:border-b lg:hidden',
|
||||||
props.className,
|
props.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -73,30 +73,39 @@ function PageWithHeader(props: PageProps) {
|
|||||||
const { Navigation, Children, MobileNavigation } = getSlotsFromPage(props);
|
const { Navigation, Children, MobileNavigation } = getSlotsFromPage(props);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex h-screen flex-1 flex-col', props.className)}>
|
|
||||||
<div
|
<div
|
||||||
className={
|
className={cn(
|
||||||
props.contentContainerClassName ?? 'flex flex-1 flex-col space-y-4'
|
'bg-background flex min-h-screen flex-1 flex-col',
|
||||||
}
|
props.className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={props.contentContainerClassName ?? 'flex flex-1 flex-col'}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-muted/40 dark:border-border dark:shadow-primary/10 flex h-14 items-center justify-between px-4 lg:justify-start lg:shadow-xs',
|
'bg-background/95 supports-[backdrop-filter]:bg-background/80 border-b',
|
||||||
{
|
{
|
||||||
'sticky top-0 z-10 backdrop-blur-md': props.sticky ?? true,
|
'sticky top-0 z-10 backdrop-blur-md': props.sticky ?? true,
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<div className="container mx-auto flex h-14 w-full items-center">
|
||||||
<div
|
<div
|
||||||
className={'hidden w-full flex-1 items-center space-x-8 lg:flex'}
|
className={
|
||||||
|
'hidden w-full min-w-0 flex-1 items-center space-x-4 lg:flex lg:px-4'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{Navigation}
|
{Navigation}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{MobileNavigation}
|
{MobileNavigation}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={'container flex flex-1 flex-col'}>{Children}</div>
|
<div className="container mx-auto flex w-full flex-1 flex-col">
|
||||||
|
{Children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -113,7 +122,15 @@ export function PageBody(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function PageNavigation(props: React.PropsWithChildren) {
|
export function PageNavigation(props: React.PropsWithChildren) {
|
||||||
return <div className={'bg-inherit'}>{props.children}</div>;
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'flex flex-1 flex-col bg-inherit group-data-[slot="sidebar-wrapper"]/sidebar-wrapper:flex-initial'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PageDescription(props: React.PropsWithChildren) {
|
export function PageDescription(props: React.PropsWithChildren) {
|
||||||
@@ -147,16 +164,25 @@ export function PageHeader({
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
className,
|
className,
|
||||||
|
displaySidebarTrigger = true,
|
||||||
}: React.PropsWithChildren<{
|
}: React.PropsWithChildren<{
|
||||||
className?: string;
|
className?: string;
|
||||||
title?: string | React.ReactNode;
|
title?: string | React.ReactNode;
|
||||||
description?: string | React.ReactNode;
|
description?: string | React.ReactNode;
|
||||||
|
displaySidebarTrigger?: boolean;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex items-center justify-between py-4', className)}>
|
<div
|
||||||
<div className={'flex flex-col gap-y-2'}>
|
className={cn(
|
||||||
<div className="flex items-center gap-x-2.5">
|
'flex flex-col gap-4 py-4 sm:py-5 lg:flex-row lg:items-center lg:justify-between',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={'flex min-w-0 flex-col gap-y-2'}>
|
||||||
|
<div className="flex flex-wrap items-center gap-x-2.5 gap-y-1.5">
|
||||||
|
<If condition={displaySidebarTrigger}>
|
||||||
<SidebarTrigger className="text-muted-foreground hover:text-secondary-foreground h-4.5 w-4.5 cursor-pointer" />
|
<SidebarTrigger className="text-muted-foreground hover:text-secondary-foreground h-4.5 w-4.5 cursor-pointer" />
|
||||||
|
</If>
|
||||||
|
|
||||||
<If condition={description}>
|
<If condition={description}>
|
||||||
<Separator
|
<Separator
|
||||||
@@ -173,8 +199,10 @@ export function PageHeader({
|
|||||||
</If>
|
</If>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex w-full flex-wrap items-center gap-2 lg:w-auto lg:justify-end">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user