Create Admin Feature
Checklist
[ ] 1. src/features/{name}/ with pages/, api/, types/
[ ] 2. api/keys.ts, api.ts, queries.ts, mutations.ts
[ ] 3. List page + Create/Edit page (or inline dialog for simple entities)
[ ] 4. Route in router (wrapped RequireRole)
[ ] 5. Sidebar entry in config/sidebar.ts with matching roles
[ ] 6. Bilingual form fields (_en/_ar) if catalog entity
Folder Structure
features/product-tag/
├── pages/
│ ├── ProductTagListPage.tsx
│ └── CreateProductTagPage.tsx # create + edit via useParams().id
├── components/ # optional
├── api/
│ ├── keys.ts
│ ├── api.ts
│ ├── queries.ts
│ └── mutations.ts
└── types/index.ts
CRUD Patterns
| Complexity | Pattern |
|---|---|
| Standard catalog | List page + shared Create/Edit page |
| Simple lookup table | Inline Dialog on list page (box-color, coupon) |
| Nested resource | /parent/:id/child + Breadcrumbs |
| Reusable form | Extract FeatureForm.tsx, parent owns mutation |
Routes
{ path: '/dashboard/product-tags', element: <RequireRole roles={['ADMIN','EDITOR']}><ListPage /></RequireRole> },
{ path: '/dashboard/product-tags/create', element: <CreatePage /> },
{ path: '/dashboard/product-tags/edit/:id', element: <CreatePage /> },
Sidebar Entry
{ title: 'Product Tags', href: '/dashboard/product-tags', roles: ['ADMIN', 'EDITOR'] }
API Functions
// api/api.ts
export const getProductTags = async (filters) => {
const params = new URLSearchParams();
params.append('page', String(filters.page ?? 1));
params.append('pageItemsCount', String(filters.pageItemsCount ?? 10));
if (filters.search) params.append('search', filters.search);
return (await api.get(`/product-tag/get-all-product-tags?${params}`)).data;
};
Backend URLs: get-all-X, create-X, update-X?id=, delete-X?id=
Mutations
export const useCreateProductTag = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: createProductTag,
onSuccess: () => { qc.invalidateQueries({ queryKey: productTagKeys.all }); toast.success('Created'); },
onError: (e) => toast.error(e?.response?.data?.message || 'Failed'),
});
};
See also: @frontend-vite/list-pages, @frontend-vite/forms, @frontend-vite/file-upload