Replace solid bg-surface background with bg-white/20 translucent
style matching the + button and company switcher. Use white text
and placeholder with opacity for consistency on the gradient header.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show a green check icon with tinted background when company is
using the global mail configuration, replacing the plain gray text.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change BaseDivider from text-subtle (which left the hr with a dark
default border) to border-line-light for a gentle themed separator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change SENT status from yellow to green in both invoice and estimate
badges. Make PAID badge more noticeable with stronger green background
(40% opacity) and semibold text. Use consistent text-status-green
token for PAID across all badge components.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Define 13 semantic color tokens (surface, text, border, hover) with
light/dark values in themes.css. Register with Tailwind via @theme inline.
Migrate all 335 Vue files from hardcoded gray/white classes to semantic
tokens. Add theme toggle (sun/moon/system) in user avatar dropdown.
Replace @tailwindcss/forms with custom form reset using theme vars.
Add status badge and alert tokens for dark mode. Theme-aware chart
grid/labels, skeleton placeholders, and editor. Inline script in
<head> prevents flash of wrong theme on load.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Display the Bouncer role title in the members list table. Move
Update App to the last position in administration settings menu.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Personalize welcome heading with user name, add descriptive subtitle,
improve invitation card styling, remove redundant logout button. Fix
hasCreateAbilities check in header to actually call the function.
Widen company switcher dropdown and improve invitation row layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make setSelectedCompany null-safe and clear stale localStorage.
Conditionally initialize company store state in bootstrap. Add
router guard to redirect no-company users to NoCompanyView while
allowing super admins through. Hide sidebar when no company.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After logout, the old auth.token and selectedCompany stayed in
localStorage. On next login, the http interceptor sent the stale
token in the Authorization header, causing all API calls to fail
with 401/419 even though the new session was valid.
After logout invalidates the session, the SPA still holds the old CSRF
cookie. Subsequent login attempts succeed but bootstrap/API calls fail
with CSRF mismatch, causing redirect back to login. Fix: fetch a fresh
CSRF cookie via /sanctum/csrf-cookie after logout completes.
When inviting an email without an InvoiceShelf account, the email now
links to a registration page (/register?invitation={token}) instead of
login. After registering, the invitation is auto-accepted.
Backend:
- InvitationRegistrationController: public details() and register()
endpoints. Registration validates token + email match, creates account,
auto-accepts invitation, returns Sanctum token.
- AuthController: login now accepts optional invitation_token param to
auto-accept invitation for existing users clicking the email link.
- CompanyInvitationMail: conditional URL based on user existence.
- Web route for /invitations/{token}/decline (email decline link).
Frontend:
- RegisterWithInvitation.vue: fetches invitation details, shows company
name + role, registration form with pre-filled email.
- Router: /register route added.
Tests: 3 new tests (invitation details, register + accept, email mismatch).
Members Index:
- "Invite Member" button opens InviteMemberModal (email + role dropdown)
- Pending invitations section shows below members table with cancel buttons
- Members store gains inviteMember, fetchPendingInvitations, cancelInvitation
CompanySwitcher:
- Shows pending invitations greyed out below active companies
- Each with Accept/Decline mini-buttons
- Accepting refreshes bootstrap and switches to new company
NoCompanyView:
- Standalone page for users with zero accepted companies
- Shows pending invitations with Accept/Decline or "no companies" message
- Route: /admin/no-company
Invitation Pinia store:
- Manages user's own pending invitations (fetchPending, accept, decline)
- Bootstrap populates invitations from API response
Global store:
- Bootstrap action stores pending_invitations from response
Match the Company Settings design pattern: sidebar navigation on desktop
with dropdown on mobile, child routes rendered via RouterView. Each tab
(General, Profile Photo, Security) is now a BaseSettingCard with its own
route under /admin/user-settings/{general,profile-photo,security}.
Backend:
- Extract user profile methods (show, update, uploadAvatar) from
CompanyController into new UserProfileController
- CompanyController now only handles company concerns (updateCompany,
uploadCompanyLogo)
- Remove Account Settings from setting_menu config
Frontend:
- New /admin/user-settings page with 3 tabs: General, Profile Photo,
Security (password change)
- User dropdown now links to /admin/user-settings instead of
/admin/settings/account-settings
- Settings sidebar defaults to Company Information as first item
- Remove old monolithic AccountSetting.vue
- Add Administration sidebar section (super-admin only) with Companies, Users, and Global Settings pages
- Add super-admin middleware, controllers, and API routes under /api/v1/super-admin/
- Allow super-admins to manage all companies and users across tenants
- Add user impersonation with short-lived tokens, audit logging, and UI banner
- Move system-level settings (Mail, PDF, Backup, Update, File Disk) from per-company to Administration > Global Settings
- Convert save_pdf_to_disk from CompanySetting to global Setting
- Add per-company mail configuration overrides (optional, falls back to global)
- Add CompanyMailConfigService to apply company mail config before sending emails
Migrate all 37 store definitions from the deprecated object-with-id
signature to the string-id-first signature required by Pinia 3:
defineStore({ id: 'name', ... }) → defineStore('name', { ... })
* refactor: add HTTP client wrapper and upgrade axios to v1
Introduce a thin HTTP wrapper (resources/scripts/http) that centralizes
axios configuration, interceptors, and auth header injection. All 43
files now import from the wrapper instead of axios directly, making
future library swaps a single-file change. Upgrade axios from 0.30.0
to 1.14.0.
* fix: restore window.Ls assignment removed during axios refactor
company.js uses window.Ls.set() to persist selected company,
which broke after the axios plugin (that set window.Ls) was deleted.
* docs: add CLAUDE.md for Claude Code guidance
* fix: handle missing settings table in installation middlewares
RedirectIfInstalled crashed with "no such table: settings" when the
database_created marker file existed but the database was empty.
Changed to use isDbCreated() which verifies actual tables, and added
try-catch around Setting queries in both middlewares.
* feat: pre-select database driver from env in installation wizard
The database step now reads DB_CONNECTION from the environment and
pre-selects the matching driver on load, including correct defaults
for hostname and port.
* feat: pre-select mail driver and config from env in installation wizard
The email step now fetches the current mail configuration on load
instead of hardcoding the driver to 'mail'. SMTP fields fall back
to Laravel config values from the environment.
* refactor: remove file-based DB marker in favor of direct DB checks
The database_created marker file was a second source of truth that
could drift out of sync with the actual database. InstallUtils now
checks the database directly via Schema::hasTable which is cached
per-request and handles all error cases gracefully.
Three bugs prevented backups stored on remote disks (Dropbox, S3, etc.)
from ever appearing in the Settings > Backup listing:
1. Typo in BackupSetting.vue: `filed_disk_id` was sent to the API
instead of `file_disk_id`, so the backend never received the selected
disk ID and always fell back to the local filesystem.
2. Wrong default disk selection in loadDisksData(): `set_as_default == 0`
selected the first disk that is NOT default (local_public), instead of
the disk that IS default. Changed to `set_as_default == 1` with a
fallback to the first disk.
3. BackupsController::index() did not call setConfig() on the FileDisk
before querying backups, so even when the correct file_disk_id arrived
it still read from the default local filesystem. Added the same disk
bootstrap logic already present in CreateBackupJob and destroy().
Made-with: Cursor
On the invoices list, when viewing page 2 or later, customer names in the
table could be wrong—opening an invoice showed a different customer than
the one displayed in that row.
Ensure we only apply API responses that match the currently requested page.
This prevents stale or out-of-order responses from overwriting the displayed
data. Also use row id as v-for key for correct Vue reconciliation.
* Fix: Actually display company currency symbol for customer charts
Related to draft #403.
The mentioned pull request was incomplete, the current state would show amounts in base currency but not use the base/company currency symbols/notation. This change addresses the issue.
* Fix: Use totalReceipts for "Receipts" value
* Upgrade the mail configuration
* Update mail configuration to match Laravel 12
* Update mail configuration to properly set none or null
* Pint code
* Upgrade Symfony Mailers
The issue was found during an Item creation inside the Invoice,
Estimates or Recurring Invoice, the same fix that was applied into the
Item creation view, now is needed into ItemModal. The root cause is that
price + tax returns an amount as float making the database fail.
Relates #377
* feat: Tax included
* Added a toggle switch in tax settings to enable the feature.
* Database migration adding tax_included field into estimates, invoices
and recurring invoices table.
* Toggle switch to enable and store the tax_included by estimates,
invoices and recurring invoices.
* In case of tax included enabled, total taxes will be recalculated and
the invoices, estimates and recurring invoices total won't be sum with
taxes.
* Apply tax included when discount_per_item/tax_per_item item is enabled.
* Custom component to show the net total when tax included is enabled.
* Update invoice and estimates pdfs with net total.
* chore: Tax included by default
A switch button inside the tax settings to enable the tax included by
default in invoices, estimates and recurring invoices.
When the `tax-per-item` is enabled and also has a tax added, during the
item creation, the price + tax returns an amount as float making the
database fail. To fix this was necessary to round the result.
* Possibility to set a fixed amount on tax types settings
* Pint and manage flat taxes on items
* Fix display errors and handle global taxes
* Tests
* Pint with PHP 8.2 cause with PHP 8.3 version it cause workflow error
* Merging percent and fixed amount into one column
* Now display the currency on SelectTaxPopup on fixed taxes