Panel: auth, 2FA, extension runtime, first-party sections (spec 0049, slices 3-8)#2553
Draft
glennjacobs wants to merge 55 commits into
Draft
Panel: auth, 2FA, extension runtime, first-party sections (spec 0049, slices 3-8)#2553glennjacobs wants to merge 55 commits into
glennjacobs wants to merge 55 commits into
Conversation
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ntics Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…totype Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ned reka-ui version
Adds the panel::auth lang group (English canonical + 15 real translations) that Tasks 6-9 controllers/pages will consume, plus a key-parity test guarding all locales against drift.
…enge Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ice 3 shipped Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Panel build assets under packages/panel/public/build were force-added in a prior commit based on a mistaken assumption that committing built assets was an established convention from slice 1. It never was: no prior commit (slices 1-9) tracked anything there, and the package's own .gitignore already excludes the path. Untrack the three files; they remain on disk as untracked local build output. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
window.LunarPanel registry (page/component/layout/translation registration with a pre-boot pending queue), Inertia page resolution that checks the runtime registry before local pages and retries after DOMContentLoaded on hard refresh, a PanelSlot.vue renderer for PHP-registered slot zones, and the add-on Vite plugin + published .d.ts that let add-on bundles compile as IIFEs sharing the panel's Vue instance.
Adds PanelLayout.vue with a collapsible sidebar (desktop) / drawer (mobile) rendering the navigation and settingsNavigation Inertia props, a top bar with a light/dark/system theme toggle (useTheme composable), and generalizes useLang to support arbitrary namespaces while keeping useAuthLang for the existing auth pages. Wires panel::nav translations into the shared Inertia lang prop and wraps Dashboard.vue in the new layout.
Self-contained SettingsShell layout driven by the settingsNavigation Inertia prop, plus a ChannelsSection with index/create/edit/delete routes and controllers. Handle auto-slugs via the Channel model mutator; delete is blocked when hasOrderHistory() is true. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sers/activity tabs Registers a Sales section with customer CRUD, nested address CRUD, and storefront user link/unlink, plus page-local Vue UI (no shared DataTable or layout dependency, per concurrent-agent boundaries). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Registers SalesSection and ChannelsSection in PanelServiceProvider::boot() so Customers and Channels appear in navigation and their routes load. Fixes an ambiguous `id` column SQL error in CustomerEditController's users() query — the customer_user pivot table has its own `id` column, so unqualified column selection collided with the joined users table.
Faithful TypeScript conversion of the Reka UI-based modal/tooltip primitives from lunar-v2-ui, preserving exact Tailwind classes, animation classes, and Reka component structure.
Faithful TypeScript port of the three components from lunar-v2-ui, adapting router-link/vue-router usage to Inertia's Link/router and Pagination's contract to Laravel's paginator meta shape.
Faithful TypeScript conversions of the prototype's Reka UI form/nav components, following the established withDefaults/script-setup conventions used by Button, TextInput, FieldLabel, Checkbox, Icon.
…ageEmpty from prototype Faithful TypeScript ports of the static Vue SPA prototype's layout/status components, adapted to the real Address model and spatie/activitylog shapes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rewrite PanelLayout/SettingsShell to match the prototype's Nav/NavBody/ SettingsNav/SettingsNavBody structure: collapsible aside with floating toggle button, ⌘\ keybind, dialog-backed mobile drawer, contextual sub-nav shown only for the active section, and a UserMenu footer (theme picker + sign out) wired to real auth/theme data instead of prototype mock data. Collapse/drawer state persists via a new useNavState composable, mirroring useTheme's localStorage pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace the plain table/native confirm/native select on the Channels list and edit pages with DataTable, ConfirmDialog, Select, Toggle and StatusBadge, matching the real prototype (ChannelsList.vue / ChannelEdit.vue) now that the ported component library exists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace the rushed native-table/native-select/native-confirm Customers pages with the real ported component library: DataTable + Pagination + Select for the index, Tabs + AddressCard + ActivityTimeline + ConfirmDialog + Combobox for the edit screen, matching CustomersList.vue and CustomerDetail.vue from the design prototype. Backend contracts are unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…15 locales The en/nav.php lang file gained settings, toggle_theme, and toggle_sidebar keys but only the English file was updated, breaking the house rule that every locale must mirror English keys. Adds genuine, professionally-toned translations for all 15 non-English locales and a Pest test asserting key parity, mirroring the existing auth.php pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Introduces CreateCustomer, UpdateCustomer, DeleteCustomer, CreateCustomerAddress, UpdateCustomerAddress, DeleteCustomerAddress, LinkCustomerUser and UnlinkCustomerUser action/contract pairs, bound in ActionServiceProvider, so the panel's customer controllers can be wired to the core action-layer convention instead of touching Eloquent directly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Introduces CreateChannel, UpdateChannel, and DeleteChannel actions (with CreatesChannel/UpdatesChannel/DeletesChannel contracts) so channel create/update/delete goes through the action-seam convention instead of ad-hoc Eloquent calls. Both create and update clear the `default` flag on every other channel in the same operation when the incoming channel becomes default. Delete refuses to remove a channel with order history, raising ChannelActionException instead of silently no-oping. Adds a missing `default => boolean` cast to the Channel model so the flag behaves consistently with sibling models (Currency, Location, TaxZone, Url). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers boot ordering and pending-callback draining, the extension component registry's warn-once-per-missing-name behaviour, translation merging, page resolution fallbacks (registry -> local glob -> DOMContentLoaded retry), and PanelSlot's priority-ordered slot rendering. Extracts app.ts's inline page-resolution logic into runtime/pageResolver.ts so it's unit-testable without a real Inertia app or import.meta.glob; app.ts's runtime behaviour is unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Route Channels create/update/delete through CreatesChannel, UpdatesChannel, and DeletesChannel instead of direct Eloquent CRUD. Delete now catches ChannelActionException from DeletesChannel and flashes an error instead of duplicating the hasOrderHistory() guard. Fixes a latent bug where omitting status on update nulled out the NOT NULL status column. Adds comprehensive Feature coverage for index, create, edit, update, and delete (including the order-history delete guard), where none existed before.
…ensions - CustomerCreateController, CustomerEditController, CustomerAddressController and CustomerUserController now delegate mutations to the Customers action contracts instead of direct Eloquent calls. - CustomerUserController turns LinksCustomerUser's ModelNotFoundException into a validation error on the email field. - Fix a latent bug: the panel read/wrote a non-existent `contact_mail` column on addresses; the real column is `contact_email` (core migration + factory already used it). Renamed throughout the address controller/edit page. - Fix a second latent bug: filtering the customer index by group threw an "ambiguous column" SQL error because the whereHas closure queried an unqualified `id`. - CustomerIndexController now consults PanelManager::resolveExtensions() for customers.index — merges extension columns into the columns prop, applies extension search/column query hooks before pagination. - Edit.vue renders the customers.edit:main:after slot zone via PanelSlot. - Add fixture add-on (Fixtures/Customers) plus feature tests proving the slot and table-extension wiring is exercised end-to-end through the real routes, not just unit-tested against the resolver. - Add full Pest coverage for Customers index/create/edit/address/user CRUD, previously untested.
Proves the reference add-on's page, nav item, slot, and table extension actually integrate with the real Customers pages, not just in isolation. Wires LunarPanelExample into the monorepo's dev autoload so its own service provider can be registered in tests, and expands the add-on README into a full extension guide with real code snippets, the zone-naming convention, and a troubleshooting section. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds GET /panel/translations/{locale}, enumerating every panel::* lang
file for a locale and hashing their mtimes into a version string so the
frontend can cache-bust localStorage correctly. Registered outside the
authenticated route group so guest screens (login, 2FA, reset) can load
it too. Falls back to app.fallback_locale for unsupported locales.
Drops the interim 'lang' Inertia shared prop now that the endpoint
replaces it.
Installs vue-i18n and boots a global i18n instance in app.ts, loading messages from the new translations endpoint. Caches the response in localStorage under a version-hash-tagged key (with a version pointer per locale) so repeat visits render synchronously with no network wait or blank-string flash; a cold cache is awaited once before mounting. registry.ts gains onTranslationsRegistered() so add-on-pushed messages (via the existing registerTranslations()) merge into the same i18n message store, replaying anything already registered to handle either script-load order. Every useLang()/useAuthLang() call site now uses vue-i18n's useI18n(), with keys requalified to the namespace.key shape (e.g. sign_in_title -> auth.sign_in_title). Removes the now-unused useLang.ts shim.
New EmailTwoFactor service issues a hashed, single-use six-digit code (10 minute TTL) with a 30 second resend cooldown, backing the panel's fallback second factor for staff without TOTP configured. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AuthenticatedSessionController::store() no longer logs staff straight in when TOTP isn't configured — every successful credential check now stashes the pending login and redirects to the two-factor challenge, which sends an emailed code instead of a TOTP prompt. The challenge controller derives the mode (authenticator vs email) from the pending user each request rather than trusting session state, exposes an obfuscated email and resend cooldown as Inertia props, and adds a resend action reusing the existing 5-attempt rate limiter. Updates the login and password-reset tests that previously asserted instant authentication for staff with no TOTP, since that path no longer exists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the email-mode variant to TwoFactorChallenge.vue: a mail icon badge, an obfuscated-address subtitle, and a resend link with a server-seeded countdown so a page refresh doesn't reset an active cooldown. The recovery-code toggle now only renders in authenticator mode, since email-mode staff have no TOTP enrolment to recover into. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds challenge_email_subtitle, resend_code, resend_in_seconds,
code_resent, and the two_factor_email_* notification keys to all 16
panel::auth locale files. The two keys interpolated client-side
(challenge_email_subtitle, resend_in_seconds) use vue-i18n's {name}
placeholder syntax; the notification-only keys keep Laravel's :name
syntax, matching how each is actually rendered.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Avoids leaving a phantom resend cooldown and code hash behind when mail delivery throws — nothing is cached unless the notification send actually succeeds. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Skips the "Forgot password?" link in tab order (tabindex="-1") so Tab goes straight from email to password. Autofocuses the email field on mount via a new focus() exposed from TextInput, more reliable than the native autofocus attribute across both full page loads and client-side Inertia navigation to the login screen. Copyright footer now reads "LunarPHP Ltd" instead of the panel display name.
…ye toggle - Load Geist/Geist Mono via Google Fonts, matching the design prototype's index.html exactly, plus the prototype's body-level typography classes (13px base, 1.45 line height, cv11/ss01 font features) — previously "Geist" silently fell back to system fonts since it was never loaded. - Port the prototype's hero image + caption onto the real Login page (same placeholder Unsplash asset the prototype uses as a dev stand-in; swap HERO_IMAGE for a real brand asset before shipping). - Restore the forward-arrow icon on the Continue button. - Password-reveal toggle now swaps eye/eyeOff icons to reflect state, not just the aria-label (new eyeOff icon added — the prototype itself never implemented this, so this is a usability addition beyond parity).
…ubmit CodeInput auto-submits on the 6th digit, so the "Verify and sign in" button was never actually clickable in that path — by the time it would become enabled, the request had already fired. Now it only shows during the recovery-code flow, where there's no auto-submit and a real click target is needed; the code-entry path shows a small spinner + status text while the auto-submitted request is in flight, nothing otherwise.
Covers the Inertia + Vue admin, its runtime extension mechanism, the mandatory 2FA fallback, and install instructions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Continues the
lunarphp/panelpackage on top of #2551 (slices 1-2). This PR covers spec 0049's remaining slices:window.LunarPanel): page/component/translation registries consumed by add-on bundlesWHATS-NEW.mdentry documenting the new package for downstream consumersSpec:
specs/0049-inertia-panel.md(status: implemented, all slices checked off).Test plan
vendor/bin/pest --testsuite panel --parallel— 160 passedvendor/bin/pest --testsuite core --parallel— 944 passed, 1 skippedvendor/bin/pint --dirty --format agent— pass🤖 Generated with Claude Code