DevelopmentResource Framework
Scoped Creation
Scoped creation and required initial values
This guide explains how to:
- Gate the “New” button and create action by permission scope
- Declare required fields for initial creation
- Use safe drilldown title templating
Overview
- Scopes come from
public.user_permission_scopes(enabled rows, global or matchingcompany_id). - Resources can define a
createsection describing:scope: scopes required to createshowButtonScope(optional): scopes required to see the “+” buttonrequired: array of required fields for initial insertdialog(optional): custom dialog component to collect values
All notifications use the notification hook:
import { useNotification } from "@/components/notifications/base";
const { notification } = useNotification();
```typescript
### Declaring create config
In `packages/resource-framework/registries/resource-routes.ts`:
```typescript
customers: {
table: "customers",
idColumn: "customer_id",
create: {
scope: "create:customers",
showButtonScope: "create:customers", // optional; defaults to scope
required: ["name", "email"],
},
// ...
}
```typescript
When `create` is present:
- If the user lacks `showButtonScope`, the “New” button is hidden.
- If the user can see but lacks `scope`, clicking “New” shows a notification and blocks creation.
- Otherwise a dialog opens to collect `required` values and performs an insert via the Suitsbooks API.
### Where the logic lives
- `useUserScopes`: fetches and caches the user’s scopes (client-side, using `/fetch/data`).
- `CreateResourceDialog`: generic dialog to collect `required` fields; performs the insert with the prescribed API shape.
- `ResourceTable`: wires button visibility and click behavior to scopes and dialog.
### API calls (workspace conventions)
- Fetch scopes:
- POST `{APP_CONFIG.api.suitsbooks}/fetch/data`
- Headers: `X-Company-Id`, `X-Organization-Id`, `X-User-Id`, `Content-Type`, optionally `Cache-Control: no-cache`
- Insert row:
- PUT `{APP_CONFIG.api.suitsbooks}/data/insert`
- Body:
```json
{
"table_name": "TABLE",
"insert_body": { "field": "value" }
}
```typescript
### Safe drilldown title templating
If a drilldown title contains tokens like `{{CUSTOMER_NAME}}`, any undefined token is stripped and the title is trimmed:
```typescript
import { safeTemplate } from "@/packages/resource-framework";
safeTemplate("Customer {{CUSTOMER_NAME}}", { CUSTOMER_NAME: undefined }); // -> "Customer"
```typescript
Applied automatically in `ResourceDrilldown`.
### Example end-to-end
- Route declares `create` with `scope: "create:customers"` and `required: ["name", "email"]`.
- User with `create:customers` sees the “New customer” button; clicking opens the dialog.
- Missing values → `notification({ message: "Please complete required fields", success: false })`.
- Successful insert → `notification({ message: "Created successfully", success: true })` and redirect to the new drilldown.
### Notes
- Buttons follow house style: action buttons `variant="brand"`, icons from `lucide-react` sized with `variant='icon_v2'` and `size='icon_v2'`, and icon color white when used inside brand buttons.
- Dialogs use `components/responsive-dialog.tsx`.
- Country inputs must use 2-letter ISO codes if present elsewhere.