Commit Graph

760 Commits

Author SHA1 Message Date
Darko Gjorgjijoski
20085cab5d Refactor FileDisk system with per-disk unique names and disk assignments UI
Major changes to the file disk subsystem:

- Each FileDisk now gets a unique Laravel disk name (disk_{id}) instead
  of temp_{driver}, fixing the bug where multiple local disks with
  different roots overwrote each other's config.

- Move disk registration logic from FileDisk model to FileDiskService
  (registerDisk, getDiskName). Model keeps only getDecodedCredentials
  and a deprecated setConfig() wrapper.

- Add Disk Assignments admin UI (File Disk tab) with three purpose
  dropdowns: Media Storage, PDF Storage, Backup Storage. Stored as
  settings (media_disk_id, pdf_disk_id, backup_disk_id).

- Backup tab now uses the assigned backup disk instead of a per-backup
  dropdown. BackupsController refactored to use BackupService which
  centralizes disk resolution. Removed stale 4-second cache.

- Add local_public disk to config/filesystems.php so system disks
  are properly defined.

- Local disk roots stored relative to storage/app/ with hint text
  in the admin modal explaining the convention.

- Fix BaseModal watchEffect -> watch to prevent infinite request
  loops on the File Disk page.

- Fix string/number comparison for disk purpose IDs from settings.

- Add safeguards: prevent deleting disks with files, warn on
  purpose change, prevent deleting system disks.
2026-04-07 02:04:57 +02:00
Darko Gjorgjijoski
9638e02eb8 Fix customer portal not reflecting company default currency
The customer portal bootstrap now returns current_company_currency
alongside the customer's own currency. The store falls back to the
company currency when the customer has no currency assigned.

Fixes #142
2026-04-06 23:37:56 +02:00
Darko Gjorgjijoski
8508e7e1b8 Show user role in company switcher
CompanyResource now includes user_role — the authenticated user's
Bouncer role title scoped to that company (e.g. "Owner"). Displayed
as a subtitle under each company name in the switcher dropdown.
2026-04-06 23:03:29 +02:00
Darko Gjorgjijoski
9ca998e64a Add Convert to Estimate feature for invoices
New backend endpoint POST /invoices/{id}/convert-to-estimate that
creates a draft estimate from an invoice, copying items, taxes,
custom fields, and financial data. Frontend wired with dropdown
action, store method, and API service call.
2026-04-06 22:57:03 +02:00
Darko Gjorgjijoski
c328d1cd10 Mount send modals on index views and dashboard, pass missing props
SendInvoiceModal and SendEstimateModal were only mounted on detail
views. Resend from table dropdowns did nothing because the modal
component was not in the DOM. Added to index views and dashboard.

Pass canCreatePayment and canCreateEstimate props to InvoiceDropdown
from detail view and dashboard.
2026-04-06 22:56:57 +02:00
Darko Gjorgjijoski
5c0e761dfa Fix copy PDF URL and dropdown action conditions
Copy PDF URL now checks window.isSecureContext before using
navigator.clipboard, falls back to textarea+execCommand on HTTP,
and shows a success notification.

Invoice dropdown: Mark as Sent uses its own condition instead of
reusing Send, Resend hidden in detail view.

Estimate dropdown: Mark as Accepted/Rejected hidden when already in
the other terminal state, Convert to Invoice hidden on rejected
estimates. Added Convert to Estimate action for invoices.
2026-04-06 22:56:49 +02:00
Darko Gjorgjijoski
6106ac8208 Fix send modals: from email and preview rendering
fetchBasicMailConfig() was calling the wrong endpoint (company-config
instead of default config). Also, the response has no .data wrapper so
from_mail was never extracted. Fixed in all three send modals.

Estimate and payment preview blob construction now falls back to the
raw response when .data is undefined, matching the invoice modal.
2026-04-06 22:56:41 +02:00
Darko Gjorgjijoski
45f347ebef Fix global tax recalculation and fractional cent totals
Global percentage taxes are now recalculated when items or discount
change, preventing stale tax amounts. Math.round() applied to
sub_total, total, and tax in invoice/estimate submit payloads to
ensure the backend always receives whole-cent integers.
2026-04-06 22:56:31 +02:00
Darko Gjorgjijoski
b07a252523 Fix default template not pre-selected on document create
Read default_invoice_template and default_estimate_template from
useUserStore().currentUserSettings instead of relying on a parameter
that was never passed from the create views.
2026-04-06 22:56:24 +02:00
Darko Gjorgjijoski
b0b7d40c73 Fix exchange rate parity across all document types
- Fix exchange-rate service types to match actual backend response shapes
  (exchangeRate array, activeProvider success/error, used currencies as strings)
- Add ExchangeRateConverter to payments, expenses, and recurring invoices
- Set currency_id from customer currency in invoice/estimate selectCustomer()
- Load globalStore.currencies in ExchangeRateConverter on mount
- Pass driver/key/driver_config params to getSupportedCurrencies in provider modal
- Fix OpenExchangeRateDriver validateConnection to use base=USD (free plan compat)
- Fix checkActiveCurrencies SQLite whereJsonContains with array values
- Remove broken currency/companyCurrency props from ExpenseCreateView, use stores
- Show base currency equivalent in document line items and totals when exchange
  rate is active
2026-04-06 21:07:50 +02:00
Darko Gjorgjijoski
e64529468c Replace deleted_files with manifest-based updater cleanup, add release workflow
- Add manifest.json generation script (scripts/generate-manifest.php)
- Add Updater::cleanStaleFiles() that removes files not in manifest
- Add /api/v1/update/clean endpoint with backward compatibility
- Add configurable update_protected_paths in config/invoiceshelf.php
- Update frontend to use clean step instead of delete step
- Add GitHub Actions release workflow triggered on version tags
- Add .github/release.yml for auto-generated changelog categories
- Update Makefile to include manifest generation and scripts directory
2026-04-06 19:27:33 +02:00
Darko Gjorgjijoski
74b4b2df4e Finalize Typescript restructure 2026-04-06 17:59:15 +02:00
Darko Gjorgjijoski
cab785172e Add missing base components and global alias registrations
Create BaseCustomTag (dynamic tag render), BaseFormatMoney,
BaseHeading, BaseScrollPane, BaseDescriptionList/Item, BaseLabel,
BaseCustomerSelectInput, BaseSpinner, BaseRating, and status label
components. Register all renamed v2 components under their old
Base* names (BaseInputGroup->FormGroup, BasePage->Page,
BaseTable->DataTable, etc.) so templates resolve correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:45:00 +02:00
Darko Gjorgjijoski
97f88eaf2c Fix build warnings: add isImageFile util, exclude heavy components
from eager glob, clean up dynamic import conflicts

- Add missing isImageFile() to format-money utils
- Exclude BaseMultiselect, InvoicePublicPage, InvoiceInformationCard
  from eager glob in global-components.ts using negative patterns
- Short-circuit English locale in i18n to avoid redundant dynamic import
- Only en.json warning remains (intentional: English bundled inline)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:15:00 +02:00
Darko Gjorgjijoski
af92a361a5 Fix auth endpoints: LOGIN and LOGOUT are web routes, not API routes
LOGIN was posting to /api/v1/auth/login but the actual route is
POST /login (session-based web route). LOGOUT was /api/v1/auth/logout
but the actual is POST /auth/logout. All 76+ other endpoints verified
correct against routes/api.php.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:00:00 +02:00
Darko Gjorgjijoski
a46cca5cd8 Complete scripts-v2 TypeScript migration — all imports resolved,
build passes

Create all missing components (modals, dropdowns, icons, tabs, mail
drivers, customer partials), fix all @/scripts/ imports to @v2/,
wire up vite entry point and blade template. 382 files, 48883 lines.

- 27 settings components: modals (tax, payment, custom field, note,
  category, role, exchange rate, unit, mail test), dropdowns (6),
  customization tabs (4), mail driver forms (4)
- 22 icon components: 5 utility icons, 4 dashboard icons, 13 editor
  toolbar icons with typed barrel export
- 3 customer components: info, chart placeholder, custom fields single
- Fixed usePopper composable, client/format-money import patterns
- Zero remaining @/scripts/ imports in scripts-v2/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:30:00 +02:00
Darko Gjorgjijoski
812956abcc Phase 5-6: Router, plugins, entry points — scripts-v2 complete
13 files completing the TypeScript migration:
- router/ (3 files): typed guards, route meta augmentation,
  merged feature routes from all 16 modules
- plugins/ (4 files): i18n with dynamic locale loading, pinia,
  tooltip directive
- Entry points: main.ts, InvoiceShelf.ts bootstrap class,
  App.vue, global-components.ts with typed registration
- NoCompanyView and NotFoundView stubs

scripts-v2/ totals: 324 files, 42853 lines of strict TypeScript.
Zero any types. Complete feature-based architecture with typed
stores, API services, composables, and Vue components.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:00:00 +02:00
Darko Gjorgjijoski
d91f6ff2e3 Phase 4b: Remaining features — payments, expenses, recurring
invoices, members, reports, settings, customer portal, modules,
installation

82 files, 14293 lines. Completes all feature modules:
- payments: CRUD with send/preview, payment modes
- expenses: CRUD with receipt upload, categories
- recurring-invoices: full frequency logic, limit by date/count
- members: list with roles, invite modal, pending invitations
- reports: sales, profit/loss, expenses, tax with date ranges
- settings: 14 settings views, number customizer, mail config
- customer-portal: consolidated store, 8 views, portal layout
- modules: marketplace index, detail/install, module cards
- installation: 8-step wizard with requirements/db/mail/account

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 07:30:00 +02:00
Darko Gjorgjijoski
774b2614f0 Phase 4a: Feature modules — layouts, auth, admin, dashboard,
customers, items, invoices, estimates, shared document form

77 files, 14451 lines. Typed layouts (CompanyLayout, AuthLayout,
header, sidebar, company switcher), auth views (login, register,
forgot/reset password), admin feature (dashboard, companies, users,
settings with typed store), company features (dashboard with chart/
stats, customers CRUD, items CRUD, invoices CRUD with full store,
estimates CRUD with full store), and shared document form components
(items table, item row, totals, notes, tax popup, template select,
exchange rate converter, calculation composable).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 06:30:00 +02:00
Darko Gjorgjijoski
e43e515614 Phase 3: Typed Vue components in scripts-v2/
Migrate all shared components to TypeScript SFCs with script setup
lang=ts. 72 files, 7144 lines, zero any types.

- components/base/ (42 files): Button, Input, Textarea, Checkbox,
  Radio, Switch, Badge, Card, Modal, Dialog, Dropdown, DatePicker,
  TimePicker, Money, FileUploader, Select, Icon, Loader, Multiselect,
  TabGroup, Wizard, CustomerSelect, ItemSelect, CustomInput, alerts,
  status badges (Invoice/Estimate/Paid/RecurringInvoice), List/ListItem
- components/table/ (3 files): DataTable, TablePagination
- components/form/ (4 files): FormGroup, FormGrid, SwitchSection
- components/layout/ (11 files): Page, PageHeader, Breadcrumb,
  FilterWrapper, EmptyPlaceholder, ContentPlaceholders, SettingCard
- components/editor/ (2 files): RichEditor with Tiptap
- components/charts/ (2 files): LineChart with Chart.js
- components/notifications/ (3 files): NotificationRoot, NotificationItem
- components/icons/ (2 files): MainLogo

All use defineProps<Props>(), defineEmits<Emits>(), typed refs,
and import domain types from types/domain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 05:45:00 +02:00
Darko Gjorgjijoski
2b996d30bf Phase 2: Typed global Pinia stores in scripts-v2/
Rewrite all 7 global stores from JS options API to TypeScript
composition API. 8 files, 1005 lines, zero any types.

- auth.store.ts: login/logout with authService
- global.store.ts: bootstrap, menus, sidebar, config fetching
- company.store.ts: company selection, admin mode, settings
- user.store.ts: current user, abilities, settings
- notification.store.ts: typed toast notifications
- dialog.store.ts: confirm dialog returning Promise<boolean>
- modal.store.ts: modal state with isEdit getter

All use async/await, typed API services, localStore utility,
and explicit ref<T> generics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 05:15:00 +02:00
Darko Gjorgjijoski
991b716b33 Phase 1: TypeScript foundation in scripts-v2/
Create the complete TypeScript foundation for the Vue 3 migration
in a parallel scripts-v2/ directory. 72 files, 5430 lines, zero
any types, strict mode.

- types/ (21 files): Domain interfaces for all 17 entities derived
  from actual Laravel models and API resources. Enums for all
  statuses. Generic API response wrappers.
- api/ (29 files): Typed axios client with interceptors, endpoint
  constants from routes/api.php, 25 typed service classes covering
  every API endpoint.
- composables/ (14 files): Vue 3 composition functions for auth,
  notifications, dialogs, modals, pagination, filters, currency,
  dates, theme, sidebar, company context, and permissions.
- utils/ (5 files): Pure typed utilities for money formatting,
  date formatting (date-fns), localStorage, and error handling.
- config/ (3 files): Typed ability constants, app constants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 05:00:00 +02:00
Darko Gjorgjijoski
53a0e1491d Refine sidebar collapse toggle to bottom toolbar
Move collapse button from header into a sticky bottom toolbar in
the sidebar. Chevron double-left/right icon aligned to sidebar edge.
Toolbar area uses mt-auto with border-t separator and glass backdrop,
ready for additional action buttons in the future.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 04:00:00 +02:00
Darko Gjorgjijoski
827c5c8938 Add collapsible sidebar with icon-only mode and tooltips
Desktop sidebar can be toggled between full width (w-56/w-64) and
collapsed icon-only mode (w-16). Collapsed state persists in
localStorage. Icons enlarge to w-6 h-6 when collapsed, labels
hidden, v-tooltip shows item names on hover. Group labels replaced
with thin dividers when collapsed. Toggle button pinned at bottom
with ChevronLeft/Right icon. Content area padding animates smoothly
with transition-all duration-300.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:45:00 +02:00
Darko Gjorgjijoski
9c8e4ae558 Add glass UI with backdrop blur and fix primary button colors
Apply glassmorphism to sidebar, cards, tables, modals, dropdowns,
and dialogs with semi-transparent backgrounds, backdrop-blur, and
white/15 borders. Add subtle gradient body background for the blur
to work against. Add dedicated btn-primary color tokens so primary
buttons stay bold in dark mode instead of using the brightened text
palette. Use shadow-sm to avoid heavy halos in light mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:30:00 +02:00
Darko Gjorgjijoski
7e15eb7c7a Make settings sidebar sticky and consistent across all settings pages
Add gap-8 to user settings (was missing), and sticky top-20
self-start to all three settings sidebars (company, admin, user
profile) so the menu stays fixed while the content area scrolls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:22:00 +02:00
Darko Gjorgjijoski
a10d4d2de9 Theme scrollbars for dark mode
Add global webkit and Firefox scrollbar styling using semantic
color tokens. Fix component scrollbar classes in GlobalSearchBar
and CompanySwitcher from hardcoded gray to theme-aware colors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:15:00 +02:00
Darko Gjorgjijoski
249b2759b6 Fix header gradient too light in dark mode
Add dedicated header-from/header-to color tokens that are independent
of the primary palette dark mode overrides. Dark mode header uses a
deeper indigo gradient instead of the brightened primary colors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:08:00 +02:00
Darko Gjorgjijoski
d7f88d3103 Fix + button icon color in header to white
The bulk sed migration changed the PlusIcon from text-gray-600 to
text-body, but it sits on the gradient header and should be white.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:00:00 +02:00
Darko Gjorgjijoski
960e6d3517 Polish header search bar with frosted glass styling
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>
2026-04-04 02:55:00 +02:00
Darko Gjorgjijoski
d36fbbbf27 Add green status indicator for global mail configuration
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>
2026-04-04 02:42:00 +02:00
Darko Gjorgjijoski
2a3774f437 Soften divider lines across settings pages
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>
2026-04-04 02:35:00 +02:00
Darko Gjorgjijoski
dabdd2b417 Fix status badge colors: SENT to green, PAID more visible
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>
2026-04-04 02:30:00 +02:00
Darko Gjorgjijoski
350068706c Unify form styling across invoice, estimate, and recurring invoice pages
Apply consistent rounded-xl shadow border-line-light to customer
selector, date/number fields card, items table, totals card, editor,
tax popup, and view page sidebars. Wrap right-side basic fields in
card container matching the customer card.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 02:18:00 +02:00
Darko Gjorgjijoski
88adfe0e50 Add dark mode with CSS custom property theme system
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>
2026-04-04 02:05:00 +02:00
Darko Gjorgjijoski
7fbe3d85a3 UI: Facelift for v3 2026-04-04 01:52:03 +02:00
Darko Gjorgjijoski
eb0a588164 Refactor Administration entrypoint
We moved the administration item to the company switcher in the header
2026-04-04 01:36:28 +02:00
Darko Gjorgjijoski
29b3abd317 Add role column to members table and reorder admin settings menu
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>
2026-04-04 01:00:00 +02:00
Darko Gjorgjijoski
c85051161b Improve NoCompanyView design and fix header for no-company state
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>
2026-04-04 00:48:00 +02:00
Darko Gjorgjijoski
c3a59a46db Add frontend handling for users without a company
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>
2026-04-04 00:36:00 +02:00
Darko Gjorgjijoski
c49c130e9e Remove Add Member button and Create page — members are added via invitation only 2026-04-04 00:00:53 +02:00
Darko Gjorgjijoski
03afb98452 Fix logout not clearing auth token and company from localStorage
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.
2026-04-03 23:49:47 +02:00
Darko Gjorgjijoski
acce67f514 Fix CSRF token mismatch after logout by refreshing cookie
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.
2026-04-03 23:46:07 +02:00
Darko Gjorgjijoski
8e966965f5 Add logo to invitation registration page 2026-04-03 23:41:27 +02:00
Darko Gjorgjijoski
c1994887ef Support invitations for unregistered users
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).
2026-04-03 23:26:58 +02:00
Darko Gjorgjijoski
6343b4a17f Add invitation frontend: invite modal, pending invitations, no-company view
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
2026-04-03 23:20:41 +02:00
Darko Gjorgjijoski
8a6c085288 Rename company-scoped Users to Members throughout
Complete rename across backend and frontend:
- Controller: Company/Users/UsersController -> Company/Members/MembersController
- Service: UserService -> MemberService
- Requests: UserRequest -> MemberRequest, DeleteUserRequest -> DeleteMemberRequest
- API routes: /api/v1/users -> /api/v1/members (company-scoped only)
- Sidebar menu: "Users" -> "Members"
- Frontend: views/users -> views/members, stores/users -> stores/members
- Router: users.index -> members.index, /admin/users -> /admin/members
- i18n: new "members" section with invitation-related keys
- Tests: UserTest -> MemberTest

Admin/super-admin Users (system-wide user management) remains unchanged.
2026-04-03 23:12:30 +02:00
Darko Gjorgjijoski
92a1baced4 Add company invitation system (backend)
New feature allowing company owners/admins to invite users by email with
a specific company-scoped role.

Database:
- New company_invitations table (company_id, email, role_id, token,
  status, invited_by, expires_at)

Backend:
- CompanyInvitation model with pending/forUser scopes
- InvitationService: invite, accept, decline, getPendingForUser
- CompanyInvitationMail with markdown email template
- InvitationController (company-scoped): list, send, cancel invitations
- InvitationResponseController (user-scoped): pending, accept, decline
- BootstrapController returns pending_invitations in response
- CompanyMiddleware handles zero-company users gracefully

Tests: 9 feature tests covering invite, accept, decline, cancel, expire,
duplicate prevention, and bootstrap integration.
2026-04-03 22:58:55 +02:00
Darko Gjorgjijoski
735eef6e9b Fix password update sending name and email to satisfy ProfileRequest validation 2026-04-03 17:42:14 +02:00
Darko Gjorgjijoski
4bfec37a53 Switch User Settings from horizontal tabs to sidebar layout
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}.
2026-04-03 17:41:18 +02:00