File Structure
Application structure
Copy
app/
Http/
Schemas/
Products/
ProductIndexSchema.php
ProductEditSchema.php
ProductViewSchema.php
Fragments/
View/
DetailsPanel.php
VariantsTable.php
ImagesTable.php
Form/
ProductBasics.php
PricingFields.php
InventoryFields.php
List/
ProductColumns.php
ProductFilters.php
ProductRowActions.php
Shared/
Fragments/
Fields/
AddressFields.php
MoneyFields.php
TaxFields.php
Tables/
AuditLogTable.php
NotesTable.php
Copy
Astra\Pages\ViewPage
Astra\Blocks\RelatedTableBlock
Astra\Http\Controllers\ViewPageController
Composer package structure
Copy
src/
Actions/
Action.php
Concerns/
HasAuthorization.php
HasConfirmation.php
HasPresentation.php
Presentation/
Presenter.php
ModalPresenter.php
SlideOutPresenter.php
InlinePresenter.php
Types/
LinkAction.php
SubmitAction.php
DangerousAction.php
Blocks/
Block.php
DetailsBlock.php
RelationBlock.php
Concerns/
HasHeading.php
HasActions.php
Data/
IR/
PageIR.php
BlockIR.php
FormIR.php
TableIR.php
FieldIR.php
ColumnIR.php
Descriptors/
Descriptor.php
Concerns/
HasLabel.php
HasHelpText.php
HasDefault.php
HasVisibility.php
HasRules.php
HasOptions.php
HasFormatting.php
Contracts/
DescribesValue.php
ResolvesOptions.php
Types/
TextDescriptor.php
MoneyDescriptor.php
EnumDescriptor.php
DateDescriptor.php
RelationDescriptor.php
Value/
Option.php
Money.php
Forms/
Form.php
Concerns/
HasState.php
HasValidation.php
InteractsWithActions.php
Fields/
Field.php
Text.php
Money.php
Select.php
Date.php
Relationship.php
Schema/
FormSchema.php
Http/
Controllers/
Middleware/
Requests/
Mutators/
Mutator.php
Contracts/
MutatesDescriptors.php
MutatesForms.php
MutatesTables.php
MutatesPages.php
Notifications/
Notification.php
NotificationManager.php
Channels/
Channel.php
ToastChannel.php
BannerChannel.php
ModalChannel.php
Concerns/
HasTitle.php
HasBody.php
HasLevel.php
HasActions.php
HasDuration.php
IsDismissible.php
Contracts/
SendsNotifications.php
RoutesNotifications.php
Events/
NotificationSent.php
Types/
Success.php
Info.php
Warning.php
Danger.php
Overlays/
Overlay.php
Modal.php
SlideOut.php
Concerns/
HasTitle.php
HasWidth.php
IsDismissible.php
Pages/
Page.php
ViewPage.php
CreatePage.php
EditPage.php
Concerns/
HasRecord.php
HasHeader.php
HasBlocks.php
HasTabs.php
Support/
Arr.php
Str.php
Exceptions/
AstraException.php
Normalizers/
Tables/
Table.php
Columns/
Column.php
Text.php
Money.php
Badge.php
Date.php
Concerns/
HasSorting.php
HasPagination.php
Filters/
Filter.php
SelectFilter.php
DateRangeFilter.php
Schema concept
Copy
use Astra\Admin\Pages\ViewPage;
use Astra\IR\Block;
use Astra\IR\Layout;
use Astra\IR\Action;
use Astra\IR\Badge;
use Astra\IR\Panel;
use Illuminate\Http\Request;
ViewPage::make('products.view')
->component('Admin/ViewPage')
->layout('record-view') // e.g. regions: header, summary, main
->record(fn (Request $r) => Product::query()->findOrFail($r->route('product')))
->authorize('viewAny', \Domain\Products\Models\Product::class)
->navigation(fn ($page) => NavigationItem::make('Products')
->route('admin.products.index')
->icon('box')
->group('Catalog')
->order(10)
->can('viewAny', \Domain\Products\Models\Product::class)
)
->breadcrumbs(fn ($page, $record) => BreadcrumbTrail::make()
->push('Catalog', route('admin.catalog'))
->push('Products', route('admin.products.index'))
->push($record->name, route('admin.products.view', $record))
)
->defaultBlock('variants')
->regions([
'header' => [
Block\Header::make()
->title(fn (Product $p) => $p->name)
->badges(fn (Product $p) => [
Badge::make('status')->from($p->status),
])
->actions(fn (Product $p) => [
Action::link('Edit')->to(fn (Product $p) => route('admin.products.edit', $p)),
Action::danger('Delete')->handler('products.delete'),
]),
],
'summary' => [
Block\Details::make('Details')
->fields([
Block\Display::text('sku'),
Block\Display::money('price')->currency('GBP'),
Block\Display::text('status'),
])
->columns(2),
],
'main' => [
// A content “host” block. Not “layout”, just a UI container within the main region.
Block\Panels::make()
->panels([
Panel::make('variants')->label('Variants')->schema([
Block\RelatedTable::make('Variants')
->stateKey('variants') // for querystring + client cache namespacing
->list(VariantsListPage::schema())
->scope(fn ($q, Product $p) => $q->where('product_id', $p->id))
->endpoint(fn (Product $p) => route('admin.products.variants.data', $p)),
]),
Panel::make('images')->label('Images')->schema([
Block\RelatedTable::make('Images')
->stateKey('images')
->list(ProductImagesListPage::schema())
->endpoint(fn (Product $p) => route('admin.products.images.data', $p)),
]),
]),
],
])
Schema class
Copy
namespace Astra\Http\Schemas\Products;
use Astra\Pages\ViewPage;
final class ProductViewSchema
{
public static function build(Request $request): ViewPage
{
return ViewPage::make('products.view')
->component('Astra/ViewPage')
->record(fn () =>
Product::query()->findOrFail($request->route('product'))
)
->blocks([
// …
]);
}
}
Controller
Copy
use App\Http\Schemas\Products\ProductViewSchema;
final class ProductViewController extends ViewPageController
{
protected function schema(Request $request): ViewPage
{
return ProductViewSchema::build($request);
}
}
Actions
Copy
Action::make('archive')
->label('Archive')
->danger()
->presentedAsModal(fn ($modal) => $modal
->title('Archive product?')
->description('This hides the product from the catalogue.')
->confirmText('Archive')
)
->handle('products.archive');
Action::make('edit')
->label('Edit')
->presentedAsSlideOut(fn ($drawer) => $drawer
->title('Edit product')
->width('xl')
)
->form(ProductForm::schema()); // action collects inputs