B2B-88: add starter kit structure and elements

This commit is contained in:
devmc-ee
2025-06-08 16:18:30 +03:00
parent 657a36a298
commit e7b25600cb
1280 changed files with 77893 additions and 5688 deletions

View File

@@ -0,0 +1,67 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { LayoutDashboard, Users } from 'lucide-react';
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
} from '@kit/ui/shadcn-sidebar';
import { AppLogo } from '~/components/app-logo';
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
export function AdminSidebar() {
const path = usePathname();
return (
<Sidebar collapsible="icon">
<SidebarHeader className={'m-2'}>
<AppLogo href={'/admin'} className="max-w-full" />
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Super Admin</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuButton isActive={path === '/admin'} asChild>
<Link className={'flex gap-2.5'} href={'/admin'}>
<LayoutDashboard className={'h-4'} />
<span>Dashboard</span>
</Link>
</SidebarMenuButton>
<SidebarMenuButton
isActive={path.includes('/admin/accounts')}
asChild
>
<Link
className={'flex size-full gap-2.5'}
href={'/admin/accounts'}
>
<Users className={'h-4'} />
<span>Accounts</span>
</Link>
</SidebarMenuButton>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<ProfileAccountDropdownContainer />
</SidebarFooter>
</Sidebar>
);
}

View File

@@ -0,0 +1,30 @@
import Link from 'next/link';
import { Menu } from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@kit/ui/dropdown-menu';
export function AdminMobileNavigation() {
return (
<DropdownMenu>
<DropdownMenuTrigger>
<Menu className={'h-8 w-8'} />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<Link href={'/admin'}>Home</Link>
</DropdownMenuItem>
<DropdownMenuItem>
<Link href={'/admin/accounts'}>Accounts</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -0,0 +1,47 @@
import { cache } from 'react';
import { AdminAccountPage } from '@kit/admin/components/admin-account-page';
import { AdminGuard } from '@kit/admin/components/admin-guard';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
interface Params {
params: Promise<{
id: string;
}>;
}
export const generateMetadata = async (props: Params) => {
const params = await props.params;
const account = await loadAccount(params.id);
return {
title: `Admin | ${account.name}`,
};
};
async function AccountPage(props: Params) {
const params = await props.params;
const account = await loadAccount(params.id);
return <AdminAccountPage account={account} />;
}
export default AdminGuard(AccountPage);
const loadAccount = cache(accountLoader);
async function accountLoader(id: string) {
const client = getSupabaseServerClient();
const { data, error } = await client
.from('accounts')
.select('*, memberships: accounts_memberships (*)')
.eq('id', id)
.single();
if (error) {
throw error;
}
return data;
}

View File

@@ -0,0 +1,79 @@
import { ServerDataLoader } from '@makerkit/data-loader-supabase-nextjs';
import { AdminAccountsTable } from '@kit/admin/components/admin-accounts-table';
import { AdminCreateUserDialog } from '@kit/admin/components/admin-create-user-dialog';
import { AdminGuard } from '@kit/admin/components/admin-guard';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
import { Button } from '@kit/ui/button';
import { PageBody, PageHeader } from '@kit/ui/page';
interface SearchParams {
page?: string;
account_type?: 'all' | 'team' | 'personal';
query?: string;
}
interface AdminAccountsPageProps {
searchParams: Promise<SearchParams>;
}
export const metadata = {
title: `Accounts`,
};
async function AccountsPage(props: AdminAccountsPageProps) {
const client = getSupabaseServerClient();
const searchParams = await props.searchParams;
const page = searchParams.page ? parseInt(searchParams.page) : 1;
return (
<>
<PageHeader description={<AppBreadcrumbs />}>
<div className="flex justify-end">
<AdminCreateUserDialog>
<Button data-test="admin-create-user-button">Create User</Button>
</AdminCreateUserDialog>
</div>
</PageHeader>
<PageBody>
<ServerDataLoader
table={'accounts'}
client={client}
page={page}
where={(queryBuilder) => {
const { account_type: type, query } = searchParams;
if (type && type !== 'all') {
queryBuilder.eq('is_personal_account', type === 'personal');
}
if (query) {
queryBuilder.or(`name.ilike.%${query}%,email.ilike.%${query}%`);
}
return queryBuilder;
}}
>
{({ data, page, pageSize, pageCount }) => {
return (
<AdminAccountsTable
page={page}
pageSize={pageSize}
pageCount={pageCount}
data={data}
filters={{
type: searchParams.account_type ?? 'all',
query: searchParams.query ?? '',
}}
/>
);
}}
</ServerDataLoader>
</PageBody>
</>
);
}
export default AdminGuard(AccountsPage);

44
app/admin/layout.tsx Normal file
View File

@@ -0,0 +1,44 @@
import { use } from 'react';
import { cookies } from 'next/headers';
import { Page, PageMobileNavigation, PageNavigation } from '@kit/ui/page';
import { SidebarProvider } from '@kit/ui/shadcn-sidebar';
import { AdminSidebar } from '~/admin/_components/admin-sidebar';
import { AdminMobileNavigation } from '~/admin/_components/mobile-navigation';
export const metadata = {
title: `Super Admin`,
};
export const dynamic = 'force-dynamic';
export default function AdminLayout(props: React.PropsWithChildren) {
const state = use(getLayoutState());
return (
<SidebarProvider defaultOpen={state.open}>
<Page style={'sidebar'}>
<PageNavigation>
<AdminSidebar />
</PageNavigation>
<PageMobileNavigation>
<AdminMobileNavigation />
</PageMobileNavigation>
{props.children}
</Page>
</SidebarProvider>
);
}
async function getLayoutState() {
const cookieStore = await cookies();
const sidebarOpenCookie = cookieStore.get('sidebar:state');
return {
open: sidebarOpenCookie?.value !== 'true',
};
}

3
app/admin/loading.tsx Normal file
View File

@@ -0,0 +1,3 @@
import { GlobalLoader } from '@kit/ui/global-loader';
export default GlobalLoader;

17
app/admin/page.tsx Normal file
View File

@@ -0,0 +1,17 @@
import { AdminDashboard } from '@kit/admin/components/admin-dashboard';
import { AdminGuard } from '@kit/admin/components/admin-guard';
import { PageBody, PageHeader } from '@kit/ui/page';
function AdminPage() {
return (
<>
<PageHeader description={`Super Admin`} />
<PageBody>
<AdminDashboard />
</PageBody>
</>
);
}
export default AdminGuard(AdminPage);