Remove redundant files and update pnpm lockfile

This commit is contained in:
giancarlo
2024-04-30 22:16:38 +07:00
parent 9eded69f15
commit 19aa40493f
78 changed files with 8835 additions and 10056 deletions

View File

@@ -0,0 +1,387 @@
'use client';
import { useMemo } from 'react';
import { ArrowDown, ArrowUp, Menu } from 'lucide-react';
import { Line, LineChart, ResponsiveContainer, XAxis } from 'recharts';
import { Badge } from '@kit/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@kit/ui/table';
export default function DashboardDemo() {
const mrr = useMemo(() => generateDemoData(), []);
const visitors = useMemo(() => generateDemoData(), []);
const returningVisitors = useMemo(() => generateDemoData(), []);
const churn = useMemo(() => generateDemoData(), []);
const netRevenue = useMemo(() => generateDemoData(), []);
const fees = useMemo(() => generateDemoData(), []);
const newCustomers = useMemo(() => generateDemoData(), []);
const tickets = useMemo(() => generateDemoData(), []);
const activeUsers = useMemo(() => generateDemoData(), []);
return (
<div className={'flex flex-col space-y-6 pb-36'}>
<div
className={
'grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3' +
' 2xl:grid-cols-4'
}
>
<Card>
<CardHeader>
<CardTitle className={'flex items-center justify-between'}>
<span>Monthly Recurring Revenue</span>
<Trend trend={'up'}>20%</Trend>
</CardTitle>
</CardHeader>
<CardContent>
<div className={'flex items-center justify-between'}>
<Figure>{`$${mrr[1]}`}</Figure>
</div>
<Chart data={mrr[0]} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className={'flex items-center justify-between'}>
<span>Revenue</span>
<Trend trend={'up'}>12%</Trend>
</CardTitle>
</CardHeader>
<CardContent>
<div className={'mb-4 flex items-center justify-between'}>
<Figure>{`$${netRevenue[1]}`}</Figure>
</div>
<Chart data={netRevenue[0]} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className={'flex items-center justify-between'}>
<span>Fees</span>
<Trend trend={'up'}>9%</Trend>
</CardTitle>
</CardHeader>
<CardContent>
<div className={'flex items-center justify-between'}>
<Figure>{`$${fees[1]}`}</Figure>
</div>
<Chart data={fees[0]} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className={'flex items-center justify-between'}>
<span>New Customers</span>
<Trend trend={'down'}>-25%</Trend>
</CardTitle>
</CardHeader>
<CardContent>
<div className={'flex items-center justify-between'}>
<Figure>{`${newCustomers[1]}`}</Figure>
</div>
<Chart data={newCustomers[0]} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className={'flex items-center justify-between'}>
<span>Visitors</span>
<Trend trend={'down'}>-4.3%</Trend>
</CardTitle>
</CardHeader>
<CardContent>
<div className={'flex items-center justify-between'}>
<Figure>{visitors[1]}</Figure>
</div>
<Chart data={visitors[0]} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className={'flex items-center justify-between'}>
<span>Returning Visitors</span>
<Trend trend={'stale'}>10%</Trend>
</CardTitle>
</CardHeader>
<CardContent>
<div className={'flex items-center justify-between'}>
<Figure>{returningVisitors[1]}</Figure>
</div>
<Chart data={returningVisitors[0]} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className={'flex items-center justify-between'}>
<span>Churn</span>
<Trend trend={'up'}>-10%</Trend>
</CardTitle>
</CardHeader>
<CardContent>
<div className={'flex items-center justify-between'}>
<Figure>{churn[1]}%</Figure>
</div>
<Chart data={churn[0]} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className={'flex items-center justify-between'}>
<span>Support Tickets</span>
<Trend trend={'up'}>-30%</Trend>
</CardTitle>
</CardHeader>
<CardContent>
<div className={'flex items-center justify-between'}>
<Figure>{tickets[1]}</Figure>
</div>
<Chart data={tickets[0]} />
</CardContent>
</Card>
</div>
<div>
<Card>
<CardHeader>
<CardTitle className={'flex items-center justify-between'}>
<span>Active Users</span>
<Trend trend={'up'}>10%</Trend>
</CardTitle>
</CardHeader>
<CardContent>
<div className={'flex items-center justify-between'}>
<Figure>{activeUsers[1]}</Figure>
</div>
<Chart data={activeUsers[0]} />
</CardContent>
</Card>
</div>
<div>
<Card>
<CardHeader>
<CardTitle>Customers</CardTitle>
</CardHeader>
<CardContent>
<CustomersTable />
</CardContent>
</Card>
</div>
</div>
);
}
function generateDemoData() {
const today = new Date();
const formatter = new Intl.DateTimeFormat('en-us', {
month: 'long',
year: '2-digit',
});
const data: { value: string; name: string }[] = [];
for (let n = 8; n > 0; n -= 1) {
const date = new Date(today.getFullYear(), today.getMonth() - n, 1);
data.push({
name: formatter.format(date),
value: (Math.random() * 10).toFixed(1),
});
}
const lastValue = data[data.length - 1]?.value;
return [data, lastValue] as [typeof data, string];
}
function Chart(
props: React.PropsWithChildren<{ data: { value: string; name: string }[] }>,
) {
return (
<div
className={
'h-36 py-2 duration-200 animate-in fade-in slide-in-from-left-4 slide-in-from-top-4'
}
>
<ResponsiveContainer width={'100%'} height={'100%'}>
<LineChart
width={400}
height={100}
data={props.data}
margin={{
top: 10,
right: 10,
left: 10,
bottom: 20,
}}
>
<Line
className={'text-primary'}
type="monotone"
dataKey="value"
stroke="currentColor"
strokeWidth={2}
dot={false}
/>
<XAxis
style={{ fontSize: 9 }}
axisLine={false}
tickSize={0}
dataKey="name"
height={15}
dy={10}
/>
</LineChart>
</ResponsiveContainer>
</div>
);
}
function CustomersTable() {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Customer</TableHead>
<TableHead>Plan</TableHead>
<TableHead>MRR</TableHead>
<TableHead>Logins</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Pippin Oddo</TableCell>
<TableCell>Pro</TableCell>
<TableCell>$100.2</TableCell>
<TableCell>920</TableCell>
<TableCell>
<BadgeWithTrend trend={'up'}>Healthy</BadgeWithTrend>
</TableCell>
</TableRow>
<TableRow>
<TableCell>Väinö Pánfilo</TableCell>
<TableCell>Basic</TableCell>
<TableCell>$40.6</TableCell>
<TableCell>300</TableCell>
<TableCell>
<BadgeWithTrend trend={'stale'}>Possible Churn</BadgeWithTrend>
</TableCell>
</TableRow>
<TableRow>
<TableCell>Giorgos Quinten</TableCell>
<TableCell>Pro</TableCell>
<TableCell>$2004.3</TableCell>
<TableCell>1000</TableCell>
<TableCell>
<BadgeWithTrend trend={'up'}>Healthy</BadgeWithTrend>
</TableCell>
</TableRow>
<TableRow>
<TableCell>Adhelm Otis</TableCell>
<TableCell>Basic</TableCell>
<TableCell>$0</TableCell>
<TableCell>10</TableCell>
<TableCell>
<BadgeWithTrend trend={'down'}>Churned</BadgeWithTrend>
</TableCell>
</TableRow>
</TableBody>
</Table>
);
}
function BadgeWithTrend(props: React.PropsWithChildren<{ trend: string }>) {
const className = useMemo(() => {
switch (props.trend) {
case 'up':
return 'text-green-500';
case 'down':
return 'text-destructive';
case 'stale':
return 'text-orange-500';
}
}, [props.trend]);
return (
<Badge variant={'outline'}>
<span className={className}>{props.children}</span>
</Badge>
);
}
function Figure(props: React.PropsWithChildren) {
return (
<div className={'font-heading text-4xl font-extrabold'}>
{props.children}
</div>
);
}
function Trend(
props: React.PropsWithChildren<{
trend: 'up' | 'down' | 'stale';
}>,
) {
const Icon = useMemo(() => {
switch (props.trend) {
case 'up':
return <ArrowUp className={'h-4 text-green-500'} />;
case 'down':
return <ArrowDown className={'h-4 text-destructive'} />;
case 'stale':
return <Menu className={'h-4 text-orange-500'} />;
}
}, [props.trend]);
return (
<div>
<BadgeWithTrend trend={props.trend}>
<span className={'flex items-center space-x-0.5'}>
{Icon}
<span>{props.children}</span>
</span>
</BadgeWithTrend>
</div>
);
}

View File

@@ -0,0 +1,39 @@
'use client';
import { useRouter } from 'next/navigation';
import { AccountSelector } from '@kit/accounts/account-selector';
import featureFlagsConfig from '~/config/feature-flags.config';
import pathsConfig from '~/config/paths.config';
const features = {
enableTeamCreation: featureFlagsConfig.enableTeamCreation,
};
export function TeamAccountAccountsSelector(params: {
selectedAccount: string;
accounts: Array<{
label: string | null;
value: string | null;
image: string | null;
}>;
}) {
const router = useRouter();
return (
<AccountSelector
selectedAccount={params.selectedAccount}
accounts={params.accounts}
collapsed={false}
features={features}
onAccountChange={(value) => {
const path = value
? pathsConfig.app.accountHome.replace('[account]', value)
: pathsConfig.app.home;
router.replace(path);
}}
/>
);
}

View File

@@ -0,0 +1,174 @@
'use client';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { Home, LogOut, Menu } from 'lucide-react';
import { AccountSelector } from '@kit/accounts/account-selector';
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@kit/ui/dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@kit/ui/dropdown-menu';
import { Trans } from '@kit/ui/trans';
import featureFlagsConfig from '~/config/feature-flags.config';
import pathsConfig from '~/config/paths.config';
import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config';
const features = {
enableTeamAccounts: featureFlagsConfig.enableTeamAccounts,
enableTeamCreation: featureFlagsConfig.enableTeamCreation,
};
export const TeamAccountLayoutMobileNavigation = (
props: React.PropsWithChildren<{
account: string;
}>,
) => {
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 (
<DropdownLink
key={item.path}
Icon={item.Icon}
path={item.path}
label={item.label}
/>
);
},
);
return (
<DropdownMenu>
<DropdownMenuTrigger>
<Menu className={'h-9'} />
</DropdownMenuTrigger>
<DropdownMenuContent sideOffset={10} className={'w-screen rounded-none'}>
<TeamAccountsModal />
{Links}
<DropdownMenuSeparator />
<SignOutDropdownItem onSignOut={() => signOut.mutateAsync()} />
</DropdownMenuContent>
</DropdownMenu>
);
};
function DropdownLink(
props: React.PropsWithChildren<{
path: string;
label: string;
Icon: React.ReactNode;
}>,
) {
return (
<DropdownMenuItem asChild>
<Link
href={props.path}
className={'flex h-12 w-full items-center space-x-2 px-3'}
>
{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-2'}
onClick={props.onSignOut}
>
<LogOut className={'h-4'} />
<span>
<Trans i18nKey={'common:signOut'} />
</span>
</DropdownMenuItem>
);
}
function TeamAccountsModal() {
const router = useRouter();
return (
<Dialog>
<DialogTrigger asChild>
<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'} />
</span>
</DropdownMenuItem>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans i18nKey={'common:yourAccounts'} />
</DialogTitle>
</DialogHeader>
<div className={'py-16'}>
<AccountSelector
onAccountChange={(value) => {
const path = value
? pathsConfig.app.accountHome.replace('[account]', value)
: pathsConfig.app.home;
router.replace(path);
}}
accounts={[]}
features={features}
/>
</div>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,15 @@
import { PageHeader } from '@kit/ui/page';
export function TeamAccountLayoutPageHeader(
props: React.PropsWithChildren<{
title: string | React.ReactNode;
description: string | React.ReactNode;
account: string;
}>,
) {
return (
<PageHeader title={props.title} description={props.description}>
{props.children}
</PageHeader>
);
}

View File

@@ -0,0 +1,57 @@
import { SidebarDivider, SidebarGroup, SidebarItem } from '@kit/ui/sidebar';
import { Trans } from '@kit/ui/trans';
import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config';
export function TeamAccountLayoutSidebarNavigation({
account,
}: React.PropsWithChildren<{
account: string;
}>) {
const routes = getTeamAccountSidebarConfig(account).routes;
return (
<>
{routes.map((item, index) => {
if ('divider' in item) {
return <SidebarDivider key={index} />;
}
if ('children' in item) {
return (
<SidebarGroup
key={item.label}
label={<Trans i18nKey={item.label} defaults={item.label} />}
collapsible={item.collapsible}
collapsed={item.collapsed}
>
{item.children.map((child) => {
return (
<SidebarItem
key={child.path}
end={child.end}
path={child.path}
Icon={child.Icon}
>
<Trans i18nKey={child.label} defaults={child.label} />
</SidebarItem>
);
})}
</SidebarGroup>
);
}
return (
<SidebarItem
key={item.path}
end={item.end}
path={item.path}
Icon={item.Icon}
>
<Trans i18nKey={item.label} defaults={item.label} />
</SidebarItem>
);
})}
</>
);
}

View File

@@ -0,0 +1,150 @@
'use client';
import { User } from '@supabase/supabase-js';
import { ArrowLeftCircle, ArrowRightCircle } from 'lucide-react';
import { If } from '@kit/ui/if';
import { Sidebar, SidebarContent } from '@kit/ui/sidebar';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@kit/ui/tooltip';
import { Trans } from '@kit/ui/trans';
import { cn } from '@kit/ui/utils';
import { ProfileAccountDropdownContainer } from '~/components//personal-account-dropdown-container';
import { TeamAccountAccountsSelector } from '../_components/team-account-accounts-selector';
import { TeamAccountLayoutSidebarNavigation } from './team-account-layout-sidebar-navigation';
type AccountModel = {
label: string | null;
value: string | null;
image: string | null;
};
export function TeamAccountLayoutSidebar(props: {
account: string;
accounts: AccountModel[];
collapsed: boolean;
user: User;
}) {
return (
<Sidebar collapsed={props.collapsed}>
{({ collapsed, setCollapsed }) => (
<SidebarContainer
collapsed={collapsed}
setCollapsed={setCollapsed}
account={props.account}
accounts={props.accounts}
user={props.user}
/>
)}
</Sidebar>
);
}
function SidebarContainer(props: {
account: string;
accounts: AccountModel[];
collapsed: boolean;
setCollapsed: (collapsed: boolean) => void;
collapsible?: boolean;
user: User;
}) {
const { account, accounts } = props;
return (
<>
<SidebarContent className={'mt-4 justify-center'}>
<TeamAccountAccountsSelector
selectedAccount={account}
accounts={accounts}
/>
</SidebarContent>
<SidebarContent className={`mt-5 h-[calc(100%-160px)] overflow-y-auto`}>
<TeamAccountLayoutSidebarNavigation account={account} />
</SidebarContent>
<div className={'absolute bottom-4 left-0 w-full'}>
<SidebarContent>
<div className={'flex space-x-2'}>
<ProfileAccountDropdownContainer
user={props.user}
collapsed={props.collapsed}
/>
</div>
<If condition={props.collapsible}>
<AppSidebarFooterMenu
collapsed={props.collapsed}
setCollapsed={props.setCollapsed}
/>
</If>
</SidebarContent>
</div>
</>
);
}
function AppSidebarFooterMenu(props: {
collapsed: boolean;
setCollapsed: (collapsed: boolean) => void;
}) {
return (
<CollapsibleButton
collapsed={props.collapsed}
onClick={props.setCollapsed}
/>
);
}
function CollapsibleButton({
collapsed,
onClick,
}: React.PropsWithChildren<{
collapsed: boolean;
onClick: (collapsed: boolean) => void;
}>) {
const className = cn(
`bg-background absolute -right-[10.5px] bottom-4 cursor-pointer block`,
);
const iconClassName = 'bg-background text-muted-foreground h-5 w-5';
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger
className={className}
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
onClick={() => onClick(!collapsed)}
>
<ArrowRightCircle
className={cn(iconClassName, {
hidden: !collapsed,
})}
/>
<ArrowLeftCircle
className={cn(iconClassName, {
hidden: collapsed,
})}
/>
</TooltipTrigger>
<TooltipContent sideOffset={20}>
<Trans
i18nKey={
collapsed ? 'common:expandSidebar' : 'common:collapseSidebar'
}
/>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}

View File

@@ -0,0 +1,71 @@
import {
BorderedNavigationMenu,
BorderedNavigationMenuItem,
} from '@kit/ui/bordered-navigation-menu';
import { AppLogo } from '~/components/app-logo';
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config';
import { TeamAccountAccountsSelector } from '~/home/[account]/_components/team-account-accounts-selector';
// local imports
import { TeamAccountWorkspace } from '../_lib/server/team-account-workspace.loader';
import { TeamAccountNotifications } from './team-account-notifications';
export function TeamAccountNavigationMenu(props: {
workspace: TeamAccountWorkspace;
}) {
const { account, user, accounts } = props.workspace;
const routes = getTeamAccountSidebarConfig(account.slug).routes.reduce<
Array<{
path: string;
label: string;
Icon?: React.ReactNode;
end?: boolean | undefined;
}>
>((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-4'}>
<AppLogo />
<BorderedNavigationMenu>
{routes.map((route) => (
<BorderedNavigationMenuItem {...route} key={route.path} />
))}
</BorderedNavigationMenu>
</div>
<div className={'flex justify-end space-x-4'}>
<TeamAccountAccountsSelector
selectedAccount={account.id}
accounts={accounts.map((account) => ({
label: account.name,
value: account.id,
image: account.picture_url,
}))}
/>
<TeamAccountNotifications accountId={account.id} userId={user.id} />
<ProfileAccountDropdownContainer
collapsed={true}
user={user}
account={account}
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,19 @@
import { NotificationsPopover } from '@kit/notifications/components';
import featuresFlagConfig from '~/config/feature-flags.config';
export function TeamAccountNotifications(params: {
userId: string;
accountId: string;
}) {
if (!featuresFlagConfig.enableNotifications) {
return null;
}
return (
<NotificationsPopover
accountIds={[params.userId, params.accountId]}
realtime={featuresFlagConfig.realtimeNotifications}
/>
);
}