init
This commit is contained in:
51
src/features/example-dashboard/api.ts
Normal file
51
src/features/example-dashboard/api.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { apiClient } from '@/lib/api-client';
|
||||
import type { ProductFilters, ProductsResponse } from '@/features/products/api/types';
|
||||
import type { UserFilters, UsersResponse } from '@/features/users/api/types';
|
||||
|
||||
async function getExampleProducts(filters: ProductFilters): Promise<ProductsResponse> {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (filters.page) searchParams.set('page', String(filters.page));
|
||||
if (filters.limit) searchParams.set('limit', String(filters.limit));
|
||||
if (filters.categories) searchParams.set('categories', filters.categories);
|
||||
if (filters.search) searchParams.set('search', filters.search);
|
||||
if (filters.sort) searchParams.set('sort', filters.sort);
|
||||
|
||||
return apiClient<ProductsResponse>(`/example/products?${searchParams.toString()}`);
|
||||
}
|
||||
|
||||
async function getExampleUsers(filters: UserFilters): Promise<UsersResponse> {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (filters.page) searchParams.set('page', String(filters.page));
|
||||
if (filters.limit) searchParams.set('limit', String(filters.limit));
|
||||
if (filters.roles) searchParams.set('roles', filters.roles);
|
||||
if (filters.search) searchParams.set('search', filters.search);
|
||||
if (filters.sort) searchParams.set('sort', filters.sort);
|
||||
|
||||
const query = searchParams.toString();
|
||||
return apiClient<UsersResponse>(`/example/users${query ? `?${query}` : ''}`);
|
||||
}
|
||||
|
||||
export const exampleProductKeys = {
|
||||
all: ['example-products'] as const,
|
||||
list: (filters: ProductFilters) => [...exampleProductKeys.all, 'list', filters] as const
|
||||
};
|
||||
|
||||
export const exampleUserKeys = {
|
||||
all: ['example-users'] as const,
|
||||
list: (filters: UserFilters) => [...exampleUserKeys.all, 'list', filters] as const
|
||||
};
|
||||
|
||||
export const exampleProductsQueryOptions = (filters: ProductFilters) =>
|
||||
queryOptions({
|
||||
queryKey: exampleProductKeys.list(filters),
|
||||
queryFn: () => getExampleProducts(filters)
|
||||
});
|
||||
|
||||
export const exampleUsersQueryOptions = (filters: UserFilters) =>
|
||||
queryOptions({
|
||||
queryKey: exampleUserKeys.list(filters),
|
||||
queryFn: () => getExampleUsers(filters)
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
'use client';
|
||||
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { DataTable } from '@/components/ui/table/data-table';
|
||||
import { DataTableColumnHeader } from '@/components/ui/table/data-table-column-header';
|
||||
import { DataTableToolbar } from '@/components/ui/table/data-table-toolbar';
|
||||
import { useDataTable } from '@/hooks/use-data-table';
|
||||
import { getSortingStateParser } from '@/lib/parsers';
|
||||
import { parseAsInteger, parseAsString, useQueryStates } from 'nuqs';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import type { Column, ColumnDef } from '@tanstack/react-table';
|
||||
import { ProductImagePreview } from '@/features/products/components/product-tables/product-image-preview';
|
||||
import type { Product } from '@/features/products/api/types';
|
||||
import { CATEGORY_OPTIONS } from '@/features/products/components/product-tables/options';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { exampleProductsQueryOptions } from '@/features/example-dashboard/api';
|
||||
|
||||
const columns: ColumnDef<Product>[] = [
|
||||
{
|
||||
accessorKey: 'photo_url',
|
||||
header: 'IMAGE',
|
||||
cell: ({ row }) => (
|
||||
<ProductImagePreview src={row.getValue('photo_url')} alt={row.getValue('name')} />
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'name',
|
||||
accessorKey: 'name',
|
||||
header: ({ column }: { column: Column<Product, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Name' />
|
||||
),
|
||||
cell: ({ cell }) => <div className='font-medium'>{cell.getValue<Product['name']>()}</div>,
|
||||
meta: {
|
||||
label: 'Name',
|
||||
placeholder: 'Search products...',
|
||||
variant: 'text',
|
||||
icon: Icons.text
|
||||
},
|
||||
enableColumnFilter: true
|
||||
},
|
||||
{
|
||||
id: 'category',
|
||||
accessorKey: 'category',
|
||||
enableSorting: false,
|
||||
header: ({ column }: { column: Column<Product, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Category' />
|
||||
),
|
||||
cell: ({ cell }) => (
|
||||
<Badge variant='outline' className='capitalize'>
|
||||
{cell.getValue<Product['category']>()}
|
||||
</Badge>
|
||||
),
|
||||
enableColumnFilter: true,
|
||||
meta: {
|
||||
label: 'categories',
|
||||
variant: 'multiSelect',
|
||||
options: CATEGORY_OPTIONS
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'price',
|
||||
header: 'PRICE',
|
||||
cell: ({ row }) => `$${row.original.price.toLocaleString()}`
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'DESCRIPTION'
|
||||
}
|
||||
];
|
||||
|
||||
const columnIds = columns.map((c) => c.id).filter(Boolean) as string[];
|
||||
|
||||
export function ExampleProductTable() {
|
||||
const [params] = useQueryStates({
|
||||
page: parseAsInteger.withDefault(1),
|
||||
perPage: parseAsInteger.withDefault(10),
|
||||
name: parseAsString,
|
||||
category: parseAsString,
|
||||
sort: getSortingStateParser(columnIds).withDefault([])
|
||||
});
|
||||
|
||||
const filters = {
|
||||
page: params.page,
|
||||
limit: params.perPage,
|
||||
...(params.name && { search: params.name }),
|
||||
...(params.category && { categories: params.category }),
|
||||
...(params.sort.length > 0 && { sort: JSON.stringify(params.sort) })
|
||||
};
|
||||
|
||||
const { data } = useSuspenseQuery(exampleProductsQueryOptions(filters));
|
||||
const pageCount = Math.ceil(data.total_products / params.perPage);
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: data.products,
|
||||
columns,
|
||||
pageCount,
|
||||
shallow: true
|
||||
});
|
||||
|
||||
return (
|
||||
<DataTable table={table}>
|
||||
<DataTableToolbar table={table} />
|
||||
</DataTable>
|
||||
);
|
||||
}
|
||||
118
src/features/example-dashboard/components/example-shell.tsx
Normal file
118
src/features/example-dashboard/components/example-shell.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { InfoSidebar } from '@/components/layout/info-sidebar';
|
||||
import { ThemeModeToggle } from '@/components/themes/theme-mode-toggle';
|
||||
import { ThemeSelector } from '@/components/themes/theme-selector';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InfobarProvider } from '@/components/ui/infobar';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { exampleDashboardNavItems } from '@/features/example-dashboard/config';
|
||||
|
||||
export function ExampleDashboardShell({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<div className='min-h-screen bg-[radial-gradient(circle_at_top,_hsl(var(--primary)/0.12),_transparent_38%),linear-gradient(180deg,_hsl(var(--background)),_hsl(var(--muted)/0.28))]'>
|
||||
<div className='mx-auto flex min-h-screen max-w-[1600px] flex-col lg:flex-row'>
|
||||
<aside className='border-border/60 bg-background/75 supports-[backdrop-filter]:bg-background/70 w-full border-b backdrop-blur-xl lg:w-72 lg:border-r lg:border-b-0'>
|
||||
<div className='flex items-center justify-between px-5 py-5 lg:block'>
|
||||
<div>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='from-primary to-primary/70 text-primary-foreground flex h-11 w-11 items-center justify-center rounded-2xl bg-gradient-to-br shadow-lg'>
|
||||
<Icons.sparkles className='h-5 w-5' />
|
||||
</div>
|
||||
<div>
|
||||
<p className='text-sm font-medium uppercase tracking-[0.28em] text-muted-foreground'>
|
||||
Template
|
||||
</p>
|
||||
<h1 className='font-semibold'>Example Dashboard</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className='mt-3 hidden text-sm text-muted-foreground lg:block'>
|
||||
Public showcase routes for tables, charts, icons, and React Query patterns.
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant='outline' className='hidden sm:inline-flex lg:mt-4'>
|
||||
Public demo
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className='px-3 pb-4'>
|
||||
<nav className='grid gap-1'>
|
||||
{exampleDashboardNavItems.map((item) => {
|
||||
const Icon = item.icon ? Icons[item.icon] : Icons.arrowRight;
|
||||
const isActive = pathname === item.url;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.url}
|
||||
href={item.url}
|
||||
className={cn(
|
||||
'group flex items-center gap-3 rounded-2xl px-3 py-3 text-sm transition-colors',
|
||||
isActive
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'hover:bg-muted/80 text-muted-foreground hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Icon className='h-4 w-4' />
|
||||
<span className='font-medium'>{item.title}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<Separator className='my-4' />
|
||||
|
||||
<div className='grid gap-2'>
|
||||
<Button asChild variant='outline' className='justify-start rounded-2xl'>
|
||||
<Link href='/setup'>
|
||||
<Icons.settings className='mr-2 h-4 w-4' />
|
||||
Setup wizard
|
||||
</Link>
|
||||
</Button>
|
||||
<p className='px-1 text-xs leading-5 text-muted-foreground'>
|
||||
Start real project setup at <code>/setup</code>, then browse these pages to see the
|
||||
template patterns in motion.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<InfobarProvider defaultOpen={false}>
|
||||
<div className='flex min-h-screen flex-1 flex-col'>
|
||||
<header className='border-border/60 bg-background/75 supports-[backdrop-filter]:bg-background/70 sticky top-0 z-20 border-b px-4 backdrop-blur-xl md:px-6'>
|
||||
<div className='flex min-h-16 items-center justify-between gap-4'>
|
||||
<div>
|
||||
<p className='text-xs uppercase tracking-[0.24em] text-muted-foreground'>
|
||||
Example Routes
|
||||
</p>
|
||||
<div className='flex items-center gap-2'>
|
||||
<h2 className='text-lg font-semibold'>Interactive template preview</h2>
|
||||
<Badge variant='secondary' className='hidden sm:inline-flex'>
|
||||
No auth required
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<ThemeModeToggle />
|
||||
<div className='hidden sm:block'>
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div className='flex flex-1 overflow-hidden'>
|
||||
<main className='flex min-w-0 flex-1 flex-col'>{children}</main>
|
||||
<InfoSidebar side='right' />
|
||||
</div>
|
||||
</div>
|
||||
</InfobarProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
113
src/features/example-dashboard/components/example-user-table.tsx
Normal file
113
src/features/example-dashboard/components/example-user-table.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
'use client';
|
||||
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { DataTable } from '@/components/ui/table/data-table';
|
||||
import { DataTableColumnHeader } from '@/components/ui/table/data-table-column-header';
|
||||
import { DataTableToolbar } from '@/components/ui/table/data-table-toolbar';
|
||||
import { useDataTable } from '@/hooks/use-data-table';
|
||||
import { getSortingStateParser } from '@/lib/parsers';
|
||||
import { parseAsInteger, parseAsString, useQueryStates } from 'nuqs';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import type { Column, ColumnDef } from '@tanstack/react-table';
|
||||
import type { User } from '@/features/users/api/types';
|
||||
import { ROLE_OPTIONS } from '@/features/users/components/users-table/options';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { exampleUsersQueryOptions } from '@/features/example-dashboard/api';
|
||||
|
||||
const columns: ColumnDef<User>[] = [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: ({ column }: { column: Column<User, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Name' />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className='flex flex-col'>
|
||||
<span className='font-medium'>{row.original.name}</span>
|
||||
<span className='text-muted-foreground text-xs'>{row.original.email}</span>
|
||||
</div>
|
||||
),
|
||||
meta: {
|
||||
label: 'Name',
|
||||
placeholder: 'Search users...',
|
||||
variant: 'text' as const,
|
||||
icon: Icons.text
|
||||
},
|
||||
enableColumnFilter: true
|
||||
},
|
||||
{
|
||||
id: 'role',
|
||||
accessorFn: (row) =>
|
||||
row.systemRole === 'super_admin' ? 'super_admin' : row.activeMembershipRole ?? 'user',
|
||||
header: ({ column }: { column: Column<User, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Role' />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const role =
|
||||
row.original.systemRole === 'super_admin' ? 'super_admin' : row.original.activeMembershipRole;
|
||||
|
||||
return (
|
||||
<Badge variant={role === 'admin' ? 'default' : 'outline'} className='capitalize'>
|
||||
{role?.replace('_', ' ') ?? 'user'}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
enableColumnFilter: true,
|
||||
meta: {
|
||||
label: 'roles',
|
||||
variant: 'multiSelect' as const,
|
||||
options: ROLE_OPTIONS
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'organizations',
|
||||
accessorFn: (row) => row.organizations.map((organization) => organization.name).join(', '),
|
||||
header: ({ column }: { column: Column<User, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Organizations' />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className='flex flex-wrap gap-1'>
|
||||
{row.original.memberships.map((membership) => (
|
||||
<Badge key={membership.organizationId} variant='secondary'>
|
||||
{membership.organizationName} ({membership.membershipRole}, {membership.businessRole.replace('_', ' ')})
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const columnIds = columns.map((c) => c.id).filter(Boolean) as string[];
|
||||
|
||||
export function ExampleUserTable() {
|
||||
const [params] = useQueryStates({
|
||||
page: parseAsInteger.withDefault(1),
|
||||
perPage: parseAsInteger.withDefault(10),
|
||||
name: parseAsString,
|
||||
role: parseAsString,
|
||||
sort: getSortingStateParser(columnIds).withDefault([])
|
||||
});
|
||||
|
||||
const filters = {
|
||||
page: params.page,
|
||||
limit: params.perPage,
|
||||
...(params.name && { search: params.name }),
|
||||
...(params.role && { roles: params.role }),
|
||||
...(params.sort.length > 0 && { sort: JSON.stringify(params.sort) })
|
||||
};
|
||||
|
||||
const { data } = useSuspenseQuery(exampleUsersQueryOptions(filters));
|
||||
const pageCount = Math.ceil(data.total_users / params.perPage);
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: data.users,
|
||||
columns,
|
||||
pageCount,
|
||||
shallow: true
|
||||
});
|
||||
|
||||
return (
|
||||
<DataTable table={table}>
|
||||
<DataTableToolbar table={table} />
|
||||
</DataTable>
|
||||
);
|
||||
}
|
||||
29
src/features/example-dashboard/config.ts
Normal file
29
src/features/example-dashboard/config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { NavItem } from '@/types';
|
||||
|
||||
export const exampleDashboardNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Overview',
|
||||
url: '/example/dashboard/overview',
|
||||
icon: 'dashboard'
|
||||
},
|
||||
{
|
||||
title: 'Products',
|
||||
url: '/example/dashboard/product',
|
||||
icon: 'product'
|
||||
},
|
||||
{
|
||||
title: 'Users',
|
||||
url: '/example/dashboard/users',
|
||||
icon: 'teams'
|
||||
},
|
||||
{
|
||||
title: 'React Query',
|
||||
url: '/example/dashboard/react-query',
|
||||
icon: 'code'
|
||||
},
|
||||
{
|
||||
title: 'Icons',
|
||||
url: '/example/dashboard/elements/icons',
|
||||
icon: 'palette'
|
||||
}
|
||||
];
|
||||
300
src/features/example-dashboard/data.ts
Normal file
300
src/features/example-dashboard/data.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
import type { Product } from '@/features/products/api/types';
|
||||
import type { User } from '@/features/users/api/types';
|
||||
|
||||
function imageFor(name: string, accent: string) {
|
||||
return `https://placehold.co/120x120/${accent}/ffffff/png?text=${encodeURIComponent(name.slice(0, 12))}`;
|
||||
}
|
||||
|
||||
const now = '2026-06-07T00:00:00.000Z';
|
||||
|
||||
export const exampleProducts: Product[] = [
|
||||
{
|
||||
id: 1,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Asset Portal', '1b4332'),
|
||||
name: 'Asset Portal',
|
||||
description: 'Starter module showing a route-handler-backed listing with filters and pagination.',
|
||||
created_at: now,
|
||||
price: 1290,
|
||||
category: 'Electronics',
|
||||
updated_at: now
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Ops Console', '2d6a4f'),
|
||||
name: 'Ops Console',
|
||||
description: 'Admin surface for approvals, task queues, and role-sensitive workflows.',
|
||||
created_at: now,
|
||||
price: 980,
|
||||
category: 'Books',
|
||||
updated_at: now
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Workspace Kit', '40916c'),
|
||||
name: 'Workspace Kit',
|
||||
description: 'Multi-tenant UI pattern package for organization switching and scoped data.',
|
||||
created_at: now,
|
||||
price: 1420,
|
||||
category: 'Furniture',
|
||||
updated_at: now
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Sales Pulse', '52796f'),
|
||||
name: 'Sales Pulse',
|
||||
description: 'Overview widgets and charts demonstrating KPI storytelling in a dashboard shell.',
|
||||
created_at: now,
|
||||
price: 760,
|
||||
category: 'Beauty Products',
|
||||
updated_at: now
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Field Notes', '606c38'),
|
||||
name: 'Field Notes',
|
||||
description: 'Lightweight operational recordkeeping page used to showcase table composition.',
|
||||
created_at: now,
|
||||
price: 240,
|
||||
category: 'Books',
|
||||
updated_at: now
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Checkout Sync', '283618'),
|
||||
name: 'Checkout Sync',
|
||||
description: 'Feature example for queue-based updates and cache invalidation patterns.',
|
||||
created_at: now,
|
||||
price: 1120,
|
||||
category: 'Electronics',
|
||||
updated_at: now
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Catalog Lens', 'bc6c25'),
|
||||
name: 'Catalog Lens',
|
||||
description: 'Search-oriented admin view ideal for practicing server-prefetch and filtering.',
|
||||
created_at: now,
|
||||
price: 640,
|
||||
category: 'Clothing',
|
||||
updated_at: now
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Support Desk', 'dda15e'),
|
||||
name: 'Support Desk',
|
||||
description: 'Internal tooling example for queues, ownership, and operational auditability.',
|
||||
created_at: now,
|
||||
price: 880,
|
||||
category: 'Toys',
|
||||
updated_at: now
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Client Ledger', '9c6644'),
|
||||
name: 'Client Ledger',
|
||||
description: 'Demonstrates data-heavy pages where columns, sorting, and filters matter.',
|
||||
created_at: now,
|
||||
price: 1540,
|
||||
category: 'Jewelry',
|
||||
updated_at: now
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Daily Brief', '582f0e'),
|
||||
name: 'Daily Brief',
|
||||
description: 'Small, opinionated module for daily metrics and recent changes.',
|
||||
created_at: now,
|
||||
price: 430,
|
||||
category: 'Groceries',
|
||||
updated_at: now
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Warehouse Pad', '7f4f24'),
|
||||
name: 'Warehouse Pad',
|
||||
description: 'Inventory-oriented sample used to show category filtering in the template.',
|
||||
created_at: now,
|
||||
price: 1180,
|
||||
category: 'Furniture',
|
||||
updated_at: now
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
organization_id: 'example-org',
|
||||
photo_url: imageFor('Pulse Board', '6f1d1b'),
|
||||
name: 'Pulse Board',
|
||||
description: 'A concise management view for experimenting with table state and URL sync.',
|
||||
created_at: now,
|
||||
price: 990,
|
||||
category: 'Electronics',
|
||||
updated_at: now
|
||||
}
|
||||
];
|
||||
|
||||
export const exampleUsers: User[] = [
|
||||
{
|
||||
id: 'user-1',
|
||||
name: 'Mina Parker',
|
||||
email: 'mina@example.com',
|
||||
systemRole: 'super_admin',
|
||||
activeMembershipRole: 'admin',
|
||||
activeOrganizationId: 'org-atlas',
|
||||
organizations: [
|
||||
{ id: 'org-atlas', name: 'Atlas Labs', role: 'admin' },
|
||||
{ id: 'org-nova', name: 'Nova Retail', role: 'admin' }
|
||||
],
|
||||
memberships: [
|
||||
{
|
||||
organizationId: 'org-atlas',
|
||||
organizationName: 'Atlas Labs',
|
||||
membershipRole: 'admin',
|
||||
businessRole: 'it_admin'
|
||||
},
|
||||
{
|
||||
organizationId: 'org-nova',
|
||||
organizationName: 'Nova Retail',
|
||||
membershipRole: 'admin',
|
||||
businessRole: 'auditor'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'user-2',
|
||||
name: 'Noah Bennett',
|
||||
email: 'noah@example.com',
|
||||
systemRole: 'user',
|
||||
activeMembershipRole: 'admin',
|
||||
activeOrganizationId: 'org-atlas',
|
||||
organizations: [{ id: 'org-atlas', name: 'Atlas Labs', role: 'admin' }],
|
||||
memberships: [
|
||||
{
|
||||
organizationId: 'org-atlas',
|
||||
organizationName: 'Atlas Labs',
|
||||
membershipRole: 'admin',
|
||||
businessRole: 'helpdesk'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'user-3',
|
||||
name: 'Lina Torres',
|
||||
email: 'lina@example.com',
|
||||
systemRole: 'user',
|
||||
activeMembershipRole: 'user',
|
||||
activeOrganizationId: 'org-atlas',
|
||||
organizations: [{ id: 'org-atlas', name: 'Atlas Labs', role: 'user' }],
|
||||
memberships: [
|
||||
{
|
||||
organizationId: 'org-atlas',
|
||||
organizationName: 'Atlas Labs',
|
||||
membershipRole: 'user',
|
||||
businessRole: 'application'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'user-4',
|
||||
name: 'Kai Morgan',
|
||||
email: 'kai@example.com',
|
||||
systemRole: 'user',
|
||||
activeMembershipRole: 'admin',
|
||||
activeOrganizationId: 'org-nova',
|
||||
organizations: [{ id: 'org-nova', name: 'Nova Retail', role: 'admin' }],
|
||||
memberships: [
|
||||
{
|
||||
organizationId: 'org-nova',
|
||||
organizationName: 'Nova Retail',
|
||||
membershipRole: 'admin',
|
||||
businessRole: 'infrastructure'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'user-5',
|
||||
name: 'Pim Srisuk',
|
||||
email: 'pim@example.com',
|
||||
systemRole: 'user',
|
||||
activeMembershipRole: 'user',
|
||||
activeOrganizationId: 'org-nova',
|
||||
organizations: [{ id: 'org-nova', name: 'Nova Retail', role: 'user' }],
|
||||
memberships: [
|
||||
{
|
||||
organizationId: 'org-nova',
|
||||
organizationName: 'Nova Retail',
|
||||
membershipRole: 'user',
|
||||
businessRole: 'viewer'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'user-6',
|
||||
name: 'Sara Kim',
|
||||
email: 'sara@example.com',
|
||||
systemRole: 'user',
|
||||
activeMembershipRole: 'user',
|
||||
activeOrganizationId: 'org-orbit',
|
||||
organizations: [{ id: 'org-orbit', name: 'Orbit Health', role: 'user' }],
|
||||
memberships: [
|
||||
{
|
||||
organizationId: 'org-orbit',
|
||||
organizationName: 'Orbit Health',
|
||||
membershipRole: 'user',
|
||||
businessRole: 'auditor'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'user-7',
|
||||
name: 'Theo Walsh',
|
||||
email: 'theo@example.com',
|
||||
systemRole: 'user',
|
||||
activeMembershipRole: 'admin',
|
||||
activeOrganizationId: 'org-orbit',
|
||||
organizations: [{ id: 'org-orbit', name: 'Orbit Health', role: 'admin' }],
|
||||
memberships: [
|
||||
{
|
||||
organizationId: 'org-orbit',
|
||||
organizationName: 'Orbit Health',
|
||||
membershipRole: 'admin',
|
||||
businessRole: 'it_admin'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'user-8',
|
||||
name: 'Mew Lert',
|
||||
email: 'mew@example.com',
|
||||
systemRole: 'user',
|
||||
activeMembershipRole: 'user',
|
||||
activeOrganizationId: 'org-atlas',
|
||||
organizations: [
|
||||
{ id: 'org-atlas', name: 'Atlas Labs', role: 'user' },
|
||||
{ id: 'org-orbit', name: 'Orbit Health', role: 'user' }
|
||||
],
|
||||
memberships: [
|
||||
{
|
||||
organizationId: 'org-atlas',
|
||||
organizationName: 'Atlas Labs',
|
||||
membershipRole: 'user',
|
||||
businessRole: 'helpdesk'
|
||||
},
|
||||
{
|
||||
organizationId: 'org-orbit',
|
||||
organizationName: 'Orbit Health',
|
||||
membershipRole: 'user',
|
||||
businessRole: 'viewer'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
Reference in New Issue
Block a user