{{-- Add Broker — docs/wireframes-v12/add-broker.html (the card at ~lines 76-259: five form sections and the footer). ## Shape ONE `.card`. Inside it, a `.form-section` per heading — each with its `.form-section-title` and the wireframe's own icon — and a `.card-footer` at the bottom. Fields are laid out by v12's `.form-row.form-row-2/-3` grids, not by Bootstrap columns, which is why no field carries a `group-class`: the row owns the columns and `.form-group` owns its own margin. ## The twin rule (recipe §9) This file and brokers/edit.blade.php are twins. Every field is written out in both, and neither includes a shared form partial. **A field added, removed or renamed here must be mirrored in edit.blade.php in the same commit.** TwinViewParityTest renders both screens and fails when their `name` sets or their required-attribute sets differ. It does not compare labels, order or validation rules, so those are on whoever edits one of the pair. ### What this screen has that its twin does not * the action (`brokers.store`) and the method (POST, not PUT); * the submit label — "Save Broker", where the twin says "Update Broker"; * the rule source — StoreBrokerRequest, where the twin names UpdateBrokerRequest (whose two unique rules ignore the row being edited); * the header wording and the breadcrumb's last crumb; * State defaults to Gujarat and Status to Active, because a fresh Broker has neither. The twin reads both off the model. * the three upload zones draw their empty prompt. The twin draws the stored filename when a slot is filled — extra markup, not an extra field, so parity is unaffected. ## Deliberate differences from the wireframe * **The Companies repeater is gone** (client instruction, CR-12 §0.3). v12 draws a `.multi-entry-list` with two `.multi-entry-item`s, a `×` remove button and an "Add Another Company" button; this screen has ONE non-repeating "Company" section holding the same three fields once. The heading is singular for the same reason. * **A Status select is added** (decision D1). v12 draws no status control anywhere in the pack; the client asked for one, and the BA placed it in the Broker Information section's Row 2 spacer slot so no new row is invented. * **The two small upload zones and the agreement zone are hand-built** rather than ``. That component always renders the large `.upload-zone` with its own generic prompt wording, and this screen needs v12's `.upload-zone-sm` for two of the three and v12's exact copy for all of them. ``'s docblock invites exactly this use; the label/asterisk/error behaviour is still the component's. * **No file size beside a stored filename on the edit twin.** v12 writes "GST_Certificate_Shah.pdf · 1.2 MB · Replace"; nothing stores a size, and asking the disk for one would be three extra round trips per page render (a HEAD each, on S3) to draw a number the user did not ask for. * **Eight of v12's asterisks are gone** (CR-12.1 A1/A2, client instruction): PAN, and every field in the Address and Bank Details sections. Five required fields are left — Contact Person Name, Phone, Email, Company Name, RERA Reg No — plus the Status select decision D1 added. Only `required` was dropped: every format rule those fields carried still applies to a value that IS entered, on the server and, where jsvalidation can render it, on the client. --}} @extends('layouts.app') @section('title', __('brokers.create_title')) @section('breadcrumbs') @endsection @section('content') @php $gstSlot = \App\Enums\BrokerFileSlot::GstCertificate; $panSlot = \App\Enums\BrokerFileSlot::PanCard; $agreementSlot = \App\Enums\BrokerFileSlot::AgreementDocument; $stateOptions = array_combine(\App\Models\Broker::STATE_OPTIONS, \App\Models\Broker::STATE_OPTIONS); @endphp {{-- Files, so multipart: without the enctype the browser posts filenames and no bytes, and nothing server-side would notice (Laravel's test client sends multipart whatever the form says). --}}
{{ __('brokers.sections.information') }}
{{-- No placeholder option and no asterisk, exactly as v12 draws it: the select opens on Individual. The column is nullable all the same — a row that predates this form has no type, and the rule is `nullable` so nothing invents one for it. --}} {{-- Email is the one field jsvalidation cannot be handed: its rule set carries a `unique`, which proengsoft renders as a REMOTE check, so the whole FIELD sits on clientRuleExclusions() — and that dropped its `required` and `email` rules with it. CR-12.1 A3 puts those two back through nrems-client-rules.js, which reads the three attributes below. The messages are the SERVER's own, from the lang layer with the same displayable attribute the FormRequest uses, so the two sides say one sentence. --}}
{{-- No asterisk since CR-12.1 A2: PAN is optional. The unique rule still applies to a PAN that IS given, server-side. --}} {{-- v12's third cell on this row is an empty `.form-group` spacer. Decision D1 puts the Status select in it rather than inventing a sixth section for one control. --}}
{{-- v12's two `.upload-zone-sm` widgets. The zone is a
{{-- v12's "Companies" repeater, reduced to ONE company by the client (CR-12 §0.3). Same three fields, same row, same order. --}}
{{ __('brokers.sections.company') }}
{{ __('brokers.sections.address') }}
{{-- v12 runs Address Line 1 across both columns of a form-row-2 with an inline `grid-column: span 2`; `.brk-col-span-2` is that rule, in the app-additions tail of nrems-wireframe.css. --}}
{{-- Gujarat is v12's `selected` option on the ADD screen. The four come from Broker::STATE_OPTIONS, which is also what the `in:` rule is built from. No asterisk since CR-12.1 A1, and the select keeps its wireframe shape — no empty option — so it simply always posts one of the four. --}}
{{ __('brokers.sections.bank') }}
{{-- No asterisk on any of the three since CR-12.1 A1: the bank block is filled in when the paperwork arrives, not at registration. Their `max:` rules are unchanged. --}}
{{-- v12's own empty spacer, kept so the two filled cells keep their thirds rather than stretching to halves. --}}
{{ __('brokers.sections.agreement') }}
{{-- v12's full-size `.upload-zone`. `d-block` because the zone is a
{{-- v12's card footer: Cancel as the outline button, then the primary action, both right-aligned inside the card's bottom rule. `.card-footer` is already the flex row, so no wrapper of its own. --}}
{{-- The three upload slots and the two unique rules are excluded from the client rule set: proengsoft can express neither a `mimetypes:` rule nor a closure, and it renders a `unique` rule as a REMOTE check against an endpoint this application does not expose. The server rules are what decide; the size cap is guarded client-side by nrems-upload-guard.js from the same BrokerFileSlot::maxKilobytes() the `max:` rule is built from. See StoreBrokerRequest::clientRuleExclusions(). The list drops FIELDS, so `email` loses its `required` and `email` rules alongside the `unique` one that is the reason it is listed; nrems-client-rules.js adds those two back to that one input from the `data-validate-*` attributes above (CR-12.1 A3, shared with the user twins by CR-13.1 B1). --}} @push('scripts') {{-- The per-slot upload size guard. Reads the data-max-kb the zones render from BrokerFileSlot::maxKilobytes() and clears an over-cap pick before it can be posted — which for the 10 MB agreement is what keeps an oversized file from being discarded by PHP's post_max_size and coming back as an unexplained 419. --}} {{-- Reflects the chosen filename into its zone, through textContent — a filename is attacker-supplied text and never markup. Does nothing at all on a page carrying no upload zone. --}} {{-- Re-arms the client-side rules that clientRuleExclusions() had to drop along with the email field's remote one (CR-12.1 A3), reading them off the `data-validate-*` attributes above. Shared with the user twins since CR-13.1 B1, which is why it is a file of its own rather than a second half of the one above. Loaded AFTER pushed its generated script, so the ready callback it registers is queued behind the `.validate()` call it needs — see the file's own ordering note. --}} @endpush @endsection