B2B-88: add starter kit structure and elements
This commit is contained in:
67
app/admin/_components/admin-sidebar.tsx
Normal file
67
app/admin/_components/admin-sidebar.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
30
app/admin/_components/mobile-navigation.tsx
Normal file
30
app/admin/_components/mobile-navigation.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
47
app/admin/accounts/[id]/page.tsx
Normal file
47
app/admin/accounts/[id]/page.tsx
Normal 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;
|
||||
}
|
||||
79
app/admin/accounts/page.tsx
Normal file
79
app/admin/accounts/page.tsx
Normal 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
44
app/admin/layout.tsx
Normal 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
3
app/admin/loading.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import { GlobalLoader } from '@kit/ui/global-loader';
|
||||
|
||||
export default GlobalLoader;
|
||||
17
app/admin/page.tsx
Normal file
17
app/admin/page.tsx
Normal 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);
|
||||
Reference in New Issue
Block a user