B2B-88: add starter kit structure and elements
This commit is contained in:
45
app/home/(user)/_components/home-account-selector.tsx
Normal file
45
app/home/(user)/_components/home-account-selector.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
'use client';
|
||||
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { AccountSelector } from '@kit/accounts/account-selector';
|
||||
import { SidebarContext } from '@kit/ui/shadcn-sidebar';
|
||||
|
||||
import featureFlagsConfig from '~/config/feature-flags.config';
|
||||
import pathsConfig from '~/config/paths.config';
|
||||
|
||||
const features = {
|
||||
enableTeamCreation: featureFlagsConfig.enableTeamCreation,
|
||||
};
|
||||
|
||||
export function HomeAccountSelector(props: {
|
||||
accounts: Array<{
|
||||
label: string | null;
|
||||
value: string | null;
|
||||
image: string | null;
|
||||
}>;
|
||||
|
||||
userId: string;
|
||||
collisionPadding?: number;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const context = useContext(SidebarContext);
|
||||
|
||||
return (
|
||||
<AccountSelector
|
||||
collapsed={!context?.open}
|
||||
collisionPadding={props.collisionPadding ?? 20}
|
||||
accounts={props.accounts}
|
||||
features={features}
|
||||
userId={props.userId}
|
||||
onAccountChange={(value) => {
|
||||
if (value) {
|
||||
const path = pathsConfig.app.accountHome.replace('[account]', value);
|
||||
router.replace(path);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
61
app/home/(user)/_components/home-accounts-list.tsx
Normal file
61
app/home/(user)/_components/home-accounts-list.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { use } from 'react';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import {
|
||||
CardButton,
|
||||
CardButtonHeader,
|
||||
CardButtonTitle,
|
||||
} from '@kit/ui/card-button';
|
||||
import {
|
||||
EmptyState,
|
||||
EmptyStateButton,
|
||||
EmptyStateHeading,
|
||||
EmptyStateText,
|
||||
} from '@kit/ui/empty-state';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { loadUserWorkspace } from '../_lib/server/load-user-workspace';
|
||||
import { HomeAddAccountButton } from './home-add-account-button';
|
||||
|
||||
export function HomeAccountsList() {
|
||||
const { accounts } = use(loadUserWorkspace());
|
||||
|
||||
if (!accounts.length) {
|
||||
return <HomeAccountsListEmptyState />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{accounts.map((account) => (
|
||||
<CardButton key={account.value} asChild>
|
||||
<Link href={`/home/${account.value}`}>
|
||||
<CardButtonHeader>
|
||||
<CardButtonTitle>{account.label}</CardButtonTitle>
|
||||
</CardButtonHeader>
|
||||
</Link>
|
||||
</CardButton>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HomeAccountsListEmptyState() {
|
||||
return (
|
||||
<div className={'flex flex-1'}>
|
||||
<EmptyState>
|
||||
<EmptyStateButton asChild>
|
||||
<HomeAddAccountButton className={'mt-4'} />
|
||||
</EmptyStateButton>
|
||||
<EmptyStateHeading>
|
||||
<Trans i18nKey={'account:noTeamsYet'} />
|
||||
</EmptyStateHeading>
|
||||
<EmptyStateText>
|
||||
<Trans i18nKey={'account:createTeam'} />
|
||||
</EmptyStateText>
|
||||
</EmptyState>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
27
app/home/(user)/_components/home-add-account-button.tsx
Normal file
27
app/home/(user)/_components/home-add-account-button.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { CreateTeamAccountDialog } from '@kit/team-accounts/components';
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
export function HomeAddAccountButton(props: { className?: string }) {
|
||||
const [isAddingAccount, setIsAddingAccount] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
className={props.className}
|
||||
onClick={() => setIsAddingAccount(true)}
|
||||
>
|
||||
<Trans i18nKey={'account:createTeamButtonLabel'} />
|
||||
</Button>
|
||||
|
||||
<CreateTeamAccountDialog
|
||||
isOpen={isAddingAccount}
|
||||
setIsOpen={setIsAddingAccount}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
68
app/home/(user)/_components/home-menu-navigation.tsx
Normal file
68
app/home/(user)/_components/home-menu-navigation.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
BorderedNavigationMenu,
|
||||
BorderedNavigationMenuItem,
|
||||
} from '@kit/ui/bordered-navigation-menu';
|
||||
import { If } from '@kit/ui/if';
|
||||
|
||||
import { AppLogo } from '~/components/app-logo';
|
||||
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
|
||||
import featuresFlagConfig from '~/config/feature-flags.config';
|
||||
import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
|
||||
|
||||
// home imports
|
||||
import { HomeAccountSelector } from '../_components/home-account-selector';
|
||||
import { UserNotifications } from '../_components/user-notifications';
|
||||
import { type UserWorkspace } from '../_lib/server/load-user-workspace';
|
||||
|
||||
export function HomeMenuNavigation(props: { workspace: UserWorkspace }) {
|
||||
const { workspace, user, accounts } = props.workspace;
|
||||
|
||||
const routes = personalAccountNavigationConfig.routes.reduce<
|
||||
Array<{
|
||||
path: string;
|
||||
label: string;
|
||||
Icon?: React.ReactNode;
|
||||
end?: boolean | ((path: string) => boolean);
|
||||
}>
|
||||
>((acc, item) => {
|
||||
if ('children' in item) {
|
||||
return [...acc, ...item.children];
|
||||
}
|
||||
|
||||
if ('divider' in item) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return [...acc, item];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={'flex w-full flex-1 justify-between'}>
|
||||
<div className={'flex items-center space-x-8'}>
|
||||
<AppLogo />
|
||||
|
||||
<BorderedNavigationMenu>
|
||||
{routes.map((route) => (
|
||||
<BorderedNavigationMenuItem {...route} key={route.path} />
|
||||
))}
|
||||
</BorderedNavigationMenu>
|
||||
</div>
|
||||
|
||||
<div className={'flex justify-end space-x-2.5'}>
|
||||
<UserNotifications userId={user.id} />
|
||||
|
||||
<If condition={featuresFlagConfig.enableTeamAccounts}>
|
||||
<HomeAccountSelector userId={user.id} accounts={accounts} />
|
||||
</If>
|
||||
|
||||
<div>
|
||||
<ProfileAccountDropdownContainer
|
||||
user={user}
|
||||
account={workspace}
|
||||
showProfileName={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
122
app/home/(user)/_components/home-mobile-navigation.tsx
Normal file
122
app/home/(user)/_components/home-mobile-navigation.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { LogOut, Menu } from 'lucide-react';
|
||||
|
||||
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@kit/ui/dropdown-menu';
|
||||
import { If } from '@kit/ui/if';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import featuresFlagConfig from '~/config/feature-flags.config';
|
||||
import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
|
||||
|
||||
// home imports
|
||||
import { HomeAccountSelector } from '../_components/home-account-selector';
|
||||
import type { UserWorkspace } from '../_lib/server/load-user-workspace';
|
||||
|
||||
export function HomeMobileNavigation(props: { workspace: UserWorkspace }) {
|
||||
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 (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Menu className={'h-9'} />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent sideOffset={10} className={'w-screen rounded-none'}>
|
||||
<If condition={featuresFlagConfig.enableTeamAccounts}>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>
|
||||
<Trans i18nKey={'common:yourAccounts'} />
|
||||
</DropdownMenuLabel>
|
||||
|
||||
<HomeAccountSelector
|
||||
userId={props.workspace.user.id}
|
||||
accounts={props.workspace.accounts}
|
||||
collisionPadding={0}
|
||||
/>
|
||||
</DropdownMenuGroup>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
</If>
|
||||
|
||||
<DropdownMenuGroup>{Links}</DropdownMenuGroup>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<SignOutDropdownItem onSignOut={() => signOut.mutateAsync()} />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownLink(
|
||||
props: React.PropsWithChildren<{
|
||||
path: string;
|
||||
label: string;
|
||||
Icon: React.ReactNode;
|
||||
}>,
|
||||
) {
|
||||
return (
|
||||
<DropdownMenuItem asChild key={props.path}>
|
||||
<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>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
12
app/home/(user)/_components/home-page-header.tsx
Normal file
12
app/home/(user)/_components/home-page-header.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { PageHeader } from '@kit/ui/page';
|
||||
|
||||
export function HomeLayoutPageHeader(
|
||||
props: React.PropsWithChildren<{
|
||||
title: string | React.ReactNode;
|
||||
description: string | React.ReactNode;
|
||||
}>,
|
||||
) {
|
||||
return (
|
||||
<PageHeader description={props.description}>{props.children}</PageHeader>
|
||||
);
|
||||
}
|
||||
61
app/home/(user)/_components/home-sidebar.tsx
Normal file
61
app/home/(user)/_components/home-sidebar.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { If } from '@kit/ui/if';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarNavigation,
|
||||
} from '@kit/ui/shadcn-sidebar';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
import { AppLogo } from '~/components/app-logo';
|
||||
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
|
||||
import featuresFlagConfig from '~/config/feature-flags.config';
|
||||
import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
|
||||
import { UserNotifications } from '~/home/(user)/_components/user-notifications';
|
||||
|
||||
// home imports
|
||||
import type { UserWorkspace } from '../_lib/server/load-user-workspace';
|
||||
import { HomeAccountSelector } from './home-account-selector';
|
||||
|
||||
interface HomeSidebarProps {
|
||||
workspace: UserWorkspace;
|
||||
}
|
||||
|
||||
export function HomeSidebar(props: HomeSidebarProps) {
|
||||
const { workspace, user, accounts } = props.workspace;
|
||||
const collapsible = personalAccountNavigationConfig.sidebarCollapsedStyle;
|
||||
|
||||
return (
|
||||
<Sidebar collapsible={collapsible}>
|
||||
<SidebarHeader className={'h-16 justify-center'}>
|
||||
<div className={'flex items-center justify-between gap-x-3'}>
|
||||
<If
|
||||
condition={featuresFlagConfig.enableTeamAccounts}
|
||||
fallback={
|
||||
<AppLogo
|
||||
className={cn(
|
||||
'p-2 group-data-[minimized=true]:max-w-full group-data-[minimized=true]:py-0',
|
||||
)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<HomeAccountSelector userId={user.id} accounts={accounts} />
|
||||
</If>
|
||||
|
||||
<div className={'group-data-[minimized=true]:hidden'}>
|
||||
<UserNotifications userId={user.id} />
|
||||
</div>
|
||||
</div>
|
||||
</SidebarHeader>
|
||||
|
||||
<SidebarContent>
|
||||
<SidebarNavigation config={personalAccountNavigationConfig} />
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter>
|
||||
<ProfileAccountDropdownContainer user={user} account={workspace} />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
16
app/home/(user)/_components/user-notifications.tsx
Normal file
16
app/home/(user)/_components/user-notifications.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NotificationsPopover } from '@kit/notifications/components';
|
||||
|
||||
import featuresFlagConfig from '~/config/feature-flags.config';
|
||||
|
||||
export function UserNotifications(props: { userId: string }) {
|
||||
if (!featuresFlagConfig.enableNotifications) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<NotificationsPopover
|
||||
accountIds={[props.userId]}
|
||||
realtime={featuresFlagConfig.realtimeNotifications}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user