The frontend uses ra-core (react-admin headless) for data fetching, routing, and CRUD logic, with shadcn-admin-kit and shadcn/ui for the UI layer.
Component architecture
- •Import form inputs (
TextInput,SelectInput,ReferenceInput, etc.) from@/components/admin/, not from shadcn/ui directly. The admin layer wraps shadcn with ra-core integration (labels, validation, data binding). - •Import pure UI components (
Card,Button,Badge,Sheet, etc.) from@/components/ui/. - •Domain configuration (deal stages, note statuses, task types, company sectors) comes from
useConfigurationContext(), never hardcoded.
Resource (CRUD) conventions
Each resource follows this file structure (e.g. contacts/):
- •
ContactList.tsx— list page (desktop + mobile variants) - •
ContactShow.tsx— detail view - •
ContactEdit.tsx/ContactCreate.tsx— form pages - •
ContactInputs.tsx— shared form fields reused between create and edit - •
index.tsx— exports{ list, show, edit, create, recordRepresentation }
Resources are registered in root/CRM.tsx via <Resource name="contacts" {...contacts} />.
Data fetching
- •For standard CRUD, use ra-core hooks:
useListContext(),useShowContext(),useGetList(),useGetOne(),useGetIdentity(). - •When a query or mutation isn't covered by ra-core hooks, add a custom dataProvider method and call it via
useQuery/useMutationwithuseDataProvider<CrmDataProvider>()(e.g.dataProvider.getActivityLog()inActivityLog.tsx,dataProvider.salesCreate()inSalesCreate.tsx).
Forms
- •Forms use
Formfrom ra-core +FormToolbarfor submit/cancel actions. - •Ra-core's
Formuses React Hook Form under the hood. UseuseFormContext()for imperative operations (setValue,reset,getValues). - •Top-level resource forms use full-page
CreateBase/EditBasewithCard(e.g. contacts), orDialog(e.g. deals). - •On mobile, inline/sub-resource forms use
CreateSheet/EditSheetfrommisc/(e.g. notes, tasks). - •Split form fields into semantic sub-components (e.g.
ContactIdentityInputs,ContactPositionInputs).
Filters
- •Use
ToggleFilterButton/ActiveFilterButtoncomponents for filter UI. - •Filters apply immediately, no "apply" button.
Responsive design
- •Major pages have desktop and mobile variants. Use
useIsMobile()to branch. - •Desktop: 2-column grid layouts. Mobile: single column with
MobileHeader/MobileContent. - •Mobile lists use
InfiniteListBasefor scroll pagination.