From b7c7a32ee6ab261fde475328b9f96fc49a1e75fd Mon Sep 17 00:00:00 2001 From: isaacclad Date: Wed, 24 Jun 2026 00:40:01 +0000 Subject: [PATCH] De-fork POS chrome/assets and import CRM products only Finish removing the upstream platform fork: drop the Hosting/SSL/Domain notification classes + their mail views, the email/mailbox/domains/hosting view trees, the Mini/Afia/search/paystack partials and unused components, and the now-orphan support helpers (DomainConfig, MailboxPricing, ResellerClubLegacy), SmsService, and fork configs (afia, hosting, domain, email, mailbox, qr, mail_brands, notifications, ...). Reparent the notifications page off the hosting layout onto the POS app layout (it had referenced undefined hosting./mini. routes). Computed the removable set from a reachability scan of the POS view graph; kept views reference no removed routes/services. Branding: favicon now uses the updated favicon.ico (favicon.svg removed); signed-out fallback logo points at the POS logo. CRM import: constrain the catalogue pull to type=product so CRM services are never imported as POS products (server-side filter + defensive client skip). Test suite green (8 passed, 29 assertions); all routes resolve and every remaining Blade compiles. Co-Authored-By: Claude Opus 4.8 --- .../DomainVerifiedNotification.php | 45 -- .../HostingActivatedNotification.php | 53 -- .../HostingDeveloperAddedNotification.php | 54 -- .../HostingExpiringNotification.php | 68 -- .../HostingResourceWarningNotification.php | 47 -- .../HostingSuspendedNotification.php | 48 -- app/Notifications/SslExpiringNotification.php | 47 -- .../SslProvisionedNotification.php | 45 -- app/Services/Billing/SmsService.php | 43 -- .../Import/CrmProductImportService.php | 8 +- app/Support/DomainConfig.php | 369 --------- app/Support/MailboxPricing.php | 69 -- app/Support/ResellerClubLegacy.php | 122 --- config/afia.php | 10 - config/android_app_links.php | 25 - config/domain.php | 6 - config/email.php | 21 - config/emaildomain.php | 7 - config/hosting.php | 703 ------------------ config/mail_brands.php | 123 --- config/mailbox.php | 9 - config/notifications.php | 17 - config/qr.php | 9 - public/favicon.ico | Bin 4414 -> 270398 bytes public/favicon.svg | 31 - resources/views/auth/signed-out.blade.php | 2 +- .../views/components/btn/create.blade.php | 17 - .../views/components/btn/primary.blade.php | 16 - .../views/components/confirm-dialog.blade.php | 69 -- .../components/hosting-panel-layout.blade.php | 73 -- .../icons/multi-domain-hosting.blade.php | 6 - .../views/components/icons/qr-code.blade.php | 5 - .../icons/single-domain-hosting.blade.php | 7 - .../components/icons/unlimited.blade.php | 6 - .../views/components/input-error.blade.php | 9 - .../components/mobile-page-header.blade.php | 32 - resources/views/components/modal.blade.php | 88 --- .../views/components/user-layout.blade.php | 33 - .../user/service-topup-modal.blade.php | 124 --- .../views/email/account/billing.blade.php | 52 -- .../views/email/account/developers.blade.php | 78 -- .../views/email/account/settings.blade.php | 110 --- resources/views/email/account/team.blade.php | 90 --- .../views/email/account/wallet.blade.php | 27 - resources/views/email/dashboard.blade.php | 47 -- resources/views/email/domains/index.blade.php | 30 - resources/views/email/domains/show.blade.php | 71 -- .../views/email/mailboxes/create.blade.php | 97 --- .../views/email/mailboxes/index.blade.php | 32 - .../views/email/mailboxes/show.blade.php | 92 --- .../views/email/mailboxes/upgrade.blade.php | 52 -- resources/views/email/signed-out.blade.php | 17 - resources/views/layouts/hosting.blade.php | 56 -- resources/views/layouts/user.blade.php | 260 ------- .../notifications/domain-verified.blade.php | 90 --- .../notifications/event-programme.blade.php | 37 - .../notifications/hosting-activated.blade.php | 64 -- .../hosting-developer-added.blade.php | 47 -- .../notifications/hosting-expiring.blade.php | 56 -- .../notifications/hosting-suspended.blade.php | 59 -- .../views/mail/notifications/layout.blade.php | 404 ---------- .../mail/notifications/ssl-expiring.blade.php | 64 -- .../notifications/ssl-provisioned.blade.php | 64 -- .../mail/partials/brand-footer.blade.php | 20 - .../mail/partials/brand-header.blade.php | 7 - resources/views/notifications/index.blade.php | 12 +- .../views/partials/afia-button.blade.php | 24 - resources/views/partials/afia.blade.php | 106 --- .../views/partials/confirm-prompt.blade.php | 65 -- resources/views/partials/favicon.blade.php | 8 +- .../views/partials/ladill-pro-icon.blade.php | 2 - .../partials/mailbox-link-banner.blade.php | 46 -- .../mini-paystack-frame-footer.blade.php | 3 - .../mini-paystack-frame-header.blade.php | 15 - .../partials/mobile-bottom-nav.blade.php | 123 --- .../partials/mobile-topbar-title.blade.php | 6 - .../views/partials/paystack-sheet.blade.php | 44 -- .../views/partials/search-screen.blade.php | 100 --- .../views/partials/sidebar-support.blade.php | 23 - .../views/partials/sso-keepalive.blade.php | 21 - .../topbar-account-switcher.blade.php | 24 - .../partials/topbar-desktop-widgets.blade.php | 6 +- resources/views/partials/topbar-qr.blade.php | 29 - .../views/partials/wordpress-icon.blade.php | 2 - resources/views/welcome.blade.php | 278 ------- tests/Feature/PosCommerceTest.php | 16 + tests/Feature/PosRegisterTest.php | 2 +- 87 files changed, 34 insertions(+), 5410 deletions(-) delete mode 100644 app/Notifications/DomainVerifiedNotification.php delete mode 100644 app/Notifications/HostingActivatedNotification.php delete mode 100644 app/Notifications/HostingDeveloperAddedNotification.php delete mode 100644 app/Notifications/HostingExpiringNotification.php delete mode 100644 app/Notifications/HostingResourceWarningNotification.php delete mode 100644 app/Notifications/HostingSuspendedNotification.php delete mode 100644 app/Notifications/SslExpiringNotification.php delete mode 100644 app/Notifications/SslProvisionedNotification.php delete mode 100644 app/Services/Billing/SmsService.php delete mode 100644 app/Support/DomainConfig.php delete mode 100644 app/Support/MailboxPricing.php delete mode 100644 app/Support/ResellerClubLegacy.php delete mode 100644 config/afia.php delete mode 100644 config/android_app_links.php delete mode 100644 config/domain.php delete mode 100644 config/email.php delete mode 100644 config/emaildomain.php delete mode 100644 config/hosting.php delete mode 100644 config/mail_brands.php delete mode 100644 config/mailbox.php delete mode 100644 config/notifications.php delete mode 100644 config/qr.php delete mode 100644 public/favicon.svg delete mode 100644 resources/views/components/btn/create.blade.php delete mode 100644 resources/views/components/btn/primary.blade.php delete mode 100644 resources/views/components/confirm-dialog.blade.php delete mode 100644 resources/views/components/hosting-panel-layout.blade.php delete mode 100644 resources/views/components/icons/multi-domain-hosting.blade.php delete mode 100644 resources/views/components/icons/qr-code.blade.php delete mode 100644 resources/views/components/icons/single-domain-hosting.blade.php delete mode 100644 resources/views/components/icons/unlimited.blade.php delete mode 100644 resources/views/components/input-error.blade.php delete mode 100644 resources/views/components/mobile-page-header.blade.php delete mode 100644 resources/views/components/modal.blade.php delete mode 100644 resources/views/components/user-layout.blade.php delete mode 100644 resources/views/components/user/service-topup-modal.blade.php delete mode 100644 resources/views/email/account/billing.blade.php delete mode 100644 resources/views/email/account/developers.blade.php delete mode 100644 resources/views/email/account/settings.blade.php delete mode 100644 resources/views/email/account/team.blade.php delete mode 100644 resources/views/email/account/wallet.blade.php delete mode 100644 resources/views/email/dashboard.blade.php delete mode 100644 resources/views/email/domains/index.blade.php delete mode 100644 resources/views/email/domains/show.blade.php delete mode 100644 resources/views/email/mailboxes/create.blade.php delete mode 100644 resources/views/email/mailboxes/index.blade.php delete mode 100644 resources/views/email/mailboxes/show.blade.php delete mode 100644 resources/views/email/mailboxes/upgrade.blade.php delete mode 100644 resources/views/email/signed-out.blade.php delete mode 100644 resources/views/layouts/hosting.blade.php delete mode 100644 resources/views/layouts/user.blade.php delete mode 100644 resources/views/mail/notifications/domain-verified.blade.php delete mode 100644 resources/views/mail/notifications/event-programme.blade.php delete mode 100644 resources/views/mail/notifications/hosting-activated.blade.php delete mode 100644 resources/views/mail/notifications/hosting-developer-added.blade.php delete mode 100644 resources/views/mail/notifications/hosting-expiring.blade.php delete mode 100644 resources/views/mail/notifications/hosting-suspended.blade.php delete mode 100644 resources/views/mail/notifications/layout.blade.php delete mode 100644 resources/views/mail/notifications/ssl-expiring.blade.php delete mode 100644 resources/views/mail/notifications/ssl-provisioned.blade.php delete mode 100644 resources/views/mail/partials/brand-footer.blade.php delete mode 100644 resources/views/mail/partials/brand-header.blade.php delete mode 100644 resources/views/partials/afia-button.blade.php delete mode 100644 resources/views/partials/afia.blade.php delete mode 100644 resources/views/partials/confirm-prompt.blade.php delete mode 100644 resources/views/partials/ladill-pro-icon.blade.php delete mode 100644 resources/views/partials/mailbox-link-banner.blade.php delete mode 100644 resources/views/partials/mini-paystack-frame-footer.blade.php delete mode 100644 resources/views/partials/mini-paystack-frame-header.blade.php delete mode 100644 resources/views/partials/mobile-bottom-nav.blade.php delete mode 100644 resources/views/partials/mobile-topbar-title.blade.php delete mode 100644 resources/views/partials/paystack-sheet.blade.php delete mode 100644 resources/views/partials/search-screen.blade.php delete mode 100644 resources/views/partials/sidebar-support.blade.php delete mode 100644 resources/views/partials/sso-keepalive.blade.php delete mode 100644 resources/views/partials/topbar-account-switcher.blade.php delete mode 100644 resources/views/partials/topbar-qr.blade.php delete mode 100644 resources/views/partials/wordpress-icon.blade.php delete mode 100644 resources/views/welcome.blade.php diff --git a/app/Notifications/DomainVerifiedNotification.php b/app/Notifications/DomainVerifiedNotification.php deleted file mode 100644 index 666597c..0000000 --- a/app/Notifications/DomainVerifiedNotification.php +++ /dev/null @@ -1,45 +0,0 @@ -subject("Domain Verified: {$this->domain->host} is now active!") - ->view('mail.notifications.domain-verified', [ - 'domain' => $this->domain, - 'manageUrl' => route('user.domains.show', $this->domain), - 'emailUrl' => route('user.mailboxes.index'), - ]); - } - - public function toArray($notifiable): array - { - return [ - 'title' => 'Domain Verified', - 'message' => "Your domain {$this->domain->host} has been verified and is now active.", - 'icon' => 'success', - 'url' => route('user.domains.show', $this->domain), - ]; - } -} diff --git a/app/Notifications/HostingActivatedNotification.php b/app/Notifications/HostingActivatedNotification.php deleted file mode 100644 index 35d8bba..0000000 --- a/app/Notifications/HostingActivatedNotification.php +++ /dev/null @@ -1,53 +0,0 @@ -subject('Your hosting is now active!') - ->view('mail.notifications.hosting-activated', [ - 'planName' => $this->planName, - 'activationDate' => $this->activationDate, - 'domainName' => $this->domainName, - 'manageUrl' => $this->manageUrl ?? route('hosting.index'), - ]); - } - - public function toArray($notifiable): array - { - $message = "Your {$this->planName} hosting plan is now active"; - if ($this->domainName) { - $message .= " for {$this->domainName}"; - } - - return [ - 'title' => 'Hosting Activated', - 'message' => $message . '.', - 'icon' => 'hosting', - 'url' => $this->manageUrl ?? route('hosting.index'), - ]; - } -} diff --git a/app/Notifications/HostingDeveloperAddedNotification.php b/app/Notifications/HostingDeveloperAddedNotification.php deleted file mode 100644 index 877bb0a..0000000 --- a/app/Notifications/HostingDeveloperAddedNotification.php +++ /dev/null @@ -1,54 +0,0 @@ - $accountLabels - */ - public function __construct( - private string $ownerName, - private array $accountLabels, - private ?string $setupUrl = null - ) {} - - public function via($notifiable): array - { - return ['mail', 'database']; - } - - public function toMail($notifiable): MailMessage - { - return (new MailMessage()) - ->subject('You were added to a Ladill hosting team') - ->view('mail.notifications.hosting-developer-added', [ - 'developer' => $notifiable, - 'ownerName' => $this->ownerName, - 'accountLabels' => $this->accountLabels, - 'setupUrl' => $this->setupUrl, - 'loginUrl' => route('login'), - 'dashboardUrl' => route('dashboard'), - ]); - } - - public function toArray($notifiable): array - { - $accountCount = count($this->accountLabels); - $scope = $accountCount === 1 ? $this->accountLabels[0] : $accountCount.' hosting accounts'; - - return [ - 'title' => 'Hosting team access granted', - 'message' => "You were added by {$this->ownerName} to {$scope}.", - 'icon' => 'hosting', - 'url' => route('hosting.single-domain'), - ]; - } -} diff --git a/app/Notifications/HostingExpiringNotification.php b/app/Notifications/HostingExpiringNotification.php deleted file mode 100644 index 48e83cf..0000000 --- a/app/Notifications/HostingExpiringNotification.php +++ /dev/null @@ -1,68 +0,0 @@ -subject($this->subjectLine()) - ->view('mail.notifications.hosting-expiring', [ - 'account' => $this->account, - 'daysRemaining' => $this->daysRemaining, - 'renewUrl' => route('hosting.accounts.show', $this->account), - ]); - } - - public function toArray($notifiable): array - { - $label = $this->account->primary_domain ?: $this->account->username; - - return [ - 'title' => $this->headline(), - 'message' => "Hosting for {$label} expires in {$this->daysRemaining} days.", - 'icon' => 'hosting', - 'url' => route('hosting.accounts.show', $this->account), - ]; - } - - private function subjectLine(): string - { - $label = $this->account->primary_domain ?: $this->account->username; - - return match (true) { - $this->daysRemaining <= 1 => "Hosting expires tomorrow: {$label}", - $this->daysRemaining <= 7 => "Urgent: hosting for {$label} expires in {$this->daysRemaining} days", - default => "Hosting renewal reminder: {$label}", - }; - } - - private function headline(): string - { - return match (true) { - $this->daysRemaining <= 1 => 'Hosting expires soon', - $this->daysRemaining <= 7 => 'Urgent hosting renewal', - default => 'Hosting renewal reminder', - }; - } -} diff --git a/app/Notifications/HostingResourceWarningNotification.php b/app/Notifications/HostingResourceWarningNotification.php deleted file mode 100644 index ba80d04..0000000 --- a/app/Notifications/HostingResourceWarningNotification.php +++ /dev/null @@ -1,47 +0,0 @@ -subject($this->subjectLine) - ->greeting('Hosting resource update') - ->line($this->messageBody) - ->line('Domain: ' . ($this->account->primary_domain ?: $this->account->username)) - ->line('Resource state: ' . ucfirst((string) ($this->account->resource_status ?: 'active'))) - ->action('Manage Hosting', route('hosting.accounts.show', $this->account)); - } - - public function toArray($notifiable): array - { - return [ - 'title' => $this->subjectLine, - 'message' => $this->messageBody, - 'icon' => 'hosting', - 'url' => route('hosting.accounts.show', $this->account), - ]; - } -} diff --git a/app/Notifications/HostingSuspendedNotification.php b/app/Notifications/HostingSuspendedNotification.php deleted file mode 100644 index e164bde..0000000 --- a/app/Notifications/HostingSuspendedNotification.php +++ /dev/null @@ -1,48 +0,0 @@ -subject('Hosting suspended: '.($this->account->primary_domain ?: $this->account->username)) - ->view('mail.notifications.hosting-suspended', [ - 'account' => $this->account, - 'reason' => $this->reason, - 'dashboardUrl' => route('hosting.accounts.show', $this->account), - ]); - } - - public function toArray($notifiable): array - { - $label = $this->account->primary_domain ?: $this->account->username; - - return [ - 'title' => 'Hosting suspended', - 'message' => "Hosting for {$label} has been suspended. {$this->reason}", - 'icon' => 'hosting', - 'url' => route('hosting.accounts.show', $this->account), - ]; - } -} diff --git a/app/Notifications/SslExpiringNotification.php b/app/Notifications/SslExpiringNotification.php deleted file mode 100644 index 25e2940..0000000 --- a/app/Notifications/SslExpiringNotification.php +++ /dev/null @@ -1,47 +0,0 @@ -subject("SSL Certificate Expiring Soon: {$this->domain->host}") - ->view('mail.notifications.ssl-expiring', [ - 'domain' => $this->domain, - 'daysUntilExpiry' => $this->daysUntilExpiry, - 'expiresAt' => $this->domain->ssl_expires_at, - 'manageUrl' => route('user.domains.show', $this->domain), - ]); - } - - public function toArray($notifiable): array - { - return [ - 'title' => 'SSL Certificate Expiring', - 'message' => "Your SSL certificate for {$this->domain->host} expires in {$this->daysUntilExpiry} days.", - 'icon' => 'warning', - 'url' => route('user.domains.show', $this->domain), - ]; - } -} diff --git a/app/Notifications/SslProvisionedNotification.php b/app/Notifications/SslProvisionedNotification.php deleted file mode 100644 index 7d8246d..0000000 --- a/app/Notifications/SslProvisionedNotification.php +++ /dev/null @@ -1,45 +0,0 @@ -subject("SSL Certificate Active: {$this->domain->host}") - ->view('mail.notifications.ssl-provisioned', [ - 'domain' => $this->domain, - 'expiresAt' => $this->domain->ssl_expires_at, - 'websiteUrl' => "https://{$this->domain->host}", - ]); - } - - public function toArray($notifiable): array - { - return [ - 'title' => 'SSL Certificate Active', - 'message' => "Your SSL certificate for {$this->domain->host} is now active. Your site is secure!", - 'icon' => 'ssl', - 'url' => route('user.domains.show', $this->domain), - ]; - } -} diff --git a/app/Services/Billing/SmsService.php b/app/Services/Billing/SmsService.php deleted file mode 100644 index 8be3d00..0000000 --- a/app/Services/Billing/SmsService.php +++ /dev/null @@ -1,43 +0,0 @@ -post('https://v3.api.termii.com/api/sms/send', [ - 'api_key' => $apiKey, - 'to' => $phone, - 'from' => $senderId, - 'sms' => $message, - 'type' => 'plain', - 'channel' => 'dnd', - ]); - } catch (\Throwable $e) { - Log::warning('SMS send failed', ['to' => $phone, 'error' => $e->getMessage()]); - } - } -} diff --git a/app/Services/Import/CrmProductImportService.php b/app/Services/Import/CrmProductImportService.php index 8c35560..88b3cce 100644 --- a/app/Services/Import/CrmProductImportService.php +++ b/app/Services/Import/CrmProductImportService.php @@ -13,7 +13,8 @@ class CrmProductImportService */ public function import(string $ownerRef): array { - $response = CrmClient::for($ownerRef)->products(['active' => 1, 'per_page' => 500]); + // CRM catalogues both products and services; POS only stocks products. + $response = CrmClient::for($ownerRef)->products(['type' => 'product', 'active' => 1, 'per_page' => 500]); $rows = (array) ($response['data'] ?? []); if ($rows === []) { @@ -24,6 +25,11 @@ class CrmProductImportService $updated = 0; foreach ($rows as $row) { + // Defensive: skip services in case an older CRM API ignores the type filter. + if (($row['type'] ?? 'product') !== 'product') { + continue; + } + $name = trim((string) ($row['name'] ?? '')); if ($name === '') { continue; diff --git a/app/Support/DomainConfig.php b/app/Support/DomainConfig.php deleted file mode 100644 index 71f7d25..0000000 --- a/app/Support/DomainConfig.php +++ /dev/null @@ -1,369 +0,0 @@ - - */ - public static function featuredTlds(): array - { - $allTlds = self::allTlds(); - $featured = array_values(array_intersect(self::PRIORITY_TLDS, $allTlds)); - - if ($featured !== []) { - return $featured; - } - - return array_slice($allTlds, 0, self::DEFAULT_PAGE_SIZE); - } - - /** - * Get all available TLDs sorted by priority. - * - * @return list - */ - public static function allTlds(): array - { - $current = self::normalizeTlds(config('domain.search_tlds', [])); - $legacy = self::normalizeTlds(config('mailinfra.domain_search_tlds', [])); - - $tlds = $legacy !== [] ? $legacy : $current; - $livePricedTlds = self::livePricedTlds(); - - if ($livePricedTlds === []) { - return []; - } - - $livePricedLookup = array_flip($livePricedTlds); - $tlds = array_values(array_filter( - $tlds, - static fn (string $tld): bool => isset($livePricedLookup[$tld]) - )); - - return self::sortWithPriority($tlds); - } - - /** - * Get paginated TLDs (excluding featured ones). - * - * @return array{tlds: list, hasMore: bool, total: int} - */ - public static function paginatedTlds(int $page = 1, int $perPage = self::DEFAULT_PAGE_SIZE): array - { - $allTlds = self::allTlds(); - $featured = self::featuredTlds(); - - $remaining = array_values(array_filter($allTlds, fn ($tld) => !in_array($tld, $featured, true))); - $total = count($remaining); - - $offset = ($page - 1) * $perPage; - $tlds = array_slice($remaining, $offset, $perPage); - - return [ - 'tlds' => $tlds, - 'hasMore' => ($offset + $perPage) < $total, - 'total' => $total, - ]; - } - - /** - * @deprecated Use featuredTlds() or allTlds() instead - * @return list - */ - public static function searchTlds(): array - { - return self::featuredTlds(); - } - - /** - * Build the TLD list and query term for a search page request. - * - * @return array{query: string, tlds: list, hasMore: bool, exact_domain: string|null} - */ - public static function searchPageConfig(string $query, int $page = 1, int $perPage = self::DEFAULT_PAGE_SIZE): array - { - $query = strtolower(trim($query)); - $allTlds = self::allTlds(); - - if ($allTlds === []) { - return [ - 'query' => $query, - 'tlds' => [], - 'hasMore' => false, - 'exact_domain' => null, - ]; - } - - $exactDomain = self::normalizeExactDomain($query); - - if ($exactDomain === null) { - if ($page === 1) { - $featured = self::featuredTlds(); - $paginatedData = self::paginatedTlds(1, $perPage); - - return [ - 'query' => $query, - 'tlds' => $featured, - 'hasMore' => $paginatedData['hasMore'] || count($paginatedData['tlds']) > 0, - 'exact_domain' => null, - ]; - } - - $paginatedData = self::paginatedTlds($page - 1, $perPage); - - return [ - 'query' => $query, - 'tlds' => $paginatedData['tlds'], - 'hasMore' => $paginatedData['hasMore'], - 'exact_domain' => null, - ]; - } - - $exactTld = self::extractTld($exactDomain); - $keyword = explode('.', $exactDomain, 2)[0] ?? $exactDomain; - $firstPageTlds = array_values(array_unique(array_filter([ - $exactTld, - ...self::featuredTlds(), - ]))); - $remainingTlds = array_values(array_filter( - $allTlds, - static fn (string $tld): bool => ! in_array($tld, $firstPageTlds, true) - )); - - if ($page === 1) { - return [ - 'query' => $keyword, - 'tlds' => $firstPageTlds, - 'hasMore' => $remainingTlds !== [], - 'exact_domain' => $exactDomain, - ]; - } - - $offset = max(0, ($page - 2) * $perPage); - - return [ - 'query' => $keyword, - 'tlds' => array_slice($remainingTlds, $offset, $perPage), - 'hasMore' => ($offset + $perPage) < count($remainingTlds), - 'exact_domain' => $exactDomain, - ]; - } - - /** - * Sort domain search results by TLD priority. - * - * @param array $results - * @return array - */ - public static function sortResultsByTldPriority(array $results, ?string $query = null): array - { - $priorityMap = array_flip(self::PRIORITY_TLDS); - $exactDomain = self::normalizeExactDomain((string) $query); - - usort($results, static function (array $a, array $b) use ($priorityMap, $exactDomain): int { - $aDomain = strtolower((string) ($a['domain'] ?? '')); - $bDomain = strtolower((string) ($b['domain'] ?? '')); - - $aExact = $exactDomain !== null && $aDomain === $exactDomain ? 0 : 1; - $bExact = $exactDomain !== null && $bDomain === $exactDomain ? 0 : 1; - - if ($aExact !== $bExact) { - return $aExact <=> $bExact; - } - - $aPremium = self::isPremiumResult($a) ? 0 : 1; - $bPremium = self::isPremiumResult($b) ? 0 : 1; - - if ($aPremium !== $bPremium) { - return $aPremium <=> $bPremium; - } - - $aAvailable = ! empty($a['available']) ? 0 : 1; - $bAvailable = ! empty($b['available']) ? 0 : 1; - - if ($aAvailable !== $bAvailable) { - return $aAvailable <=> $bAvailable; - } - - $aTld = self::extractTld($a['domain'] ?? ''); - $bTld = self::extractTld($b['domain'] ?? ''); - - $aPriority = $priorityMap[$aTld] ?? PHP_INT_MAX; - $bPriority = $priorityMap[$bTld] ?? PHP_INT_MAX; - - if ($aPriority !== $bPriority) { - return $aPriority <=> $bPriority; - } - - return $aDomain <=> $bDomain; - }); - - return $results; - } - - /** - * Extract TLD from a domain name. - */ - private static function extractTld(string $domain): string - { - $parts = explode('.', strtolower($domain), 2); - - return $parts[1] ?? ''; - } - - private static function normalizeExactDomain(string $query): ?string - { - $query = strtolower(trim($query)); - - if ($query === '' || str_starts_with($query, '.') || str_ends_with($query, '.') || ! str_contains($query, '.')) { - return null; - } - - return preg_match('/^(?=.{1,253}$)(?!-)(?:[a-z0-9-]{1,63}\.)+[a-z]{2,63}$/', $query) === 1 - ? $query - : null; - } - - /** - * @param array $result - */ - private static function isPremiumResult(array $result): bool - { - $premium = $result['premium'] ?? false; - - if (is_bool($premium)) { - return $premium; - } - - return in_array(strtolower(trim((string) $premium)), ['1', 'yes', 'true', 'premium'], true); - } - - /** - * @param list $tlds - * @return list - */ - private static function sortWithPriority(array $tlds): array - { - $priorityMap = array_flip(self::PRIORITY_TLDS); - $pricingMap = self::pricingMapForTlds($tlds); - - usort($tlds, static function (string $a, string $b) use ($priorityMap, $pricingMap): int { - $aPriority = $priorityMap[$a] ?? PHP_INT_MAX; - $bPriority = $priorityMap[$b] ?? PHP_INT_MAX; - - if ($aPriority !== $bPriority) { - return $aPriority <=> $bPriority; - } - - $aPrice = (int) ($pricingMap[$a]['register'] ?? 0); - $bPrice = (int) ($pricingMap[$b]['register'] ?? 0); - - if ($aPrice !== $bPrice) { - return $bPrice <=> $aPrice; - } - - return $a <=> $b; - }); - - return $tlds; - } - - /** - * @return array - */ - public static function resellerClubTldProductKeys(): array - { - return array_replace( - self::normalizeProductKeyMap(config('domain.resellerclub.tld_product_keys', [])), - self::normalizeProductKeyMap(config('mailinfra.resellerclub_tld_product_keys', [])) - ); - } - - public static function resellerClubProductKeyCacheTtlSeconds(): int - { - $ttl = (int) config('domain.resellerclub.product_key_cache_ttl_seconds', 86400); - - return $ttl > 0 ? $ttl : 86400; - } - - /** - * @param mixed $value - * @return list - */ - private static function normalizeTlds(mixed $value): array - { - if (! is_array($value)) { - return []; - } - - return array_values(array_unique(array_filter(array_map( - static fn ($tld): string => ltrim(strtolower(trim((string) $tld)), '.'), - $value - )))); - } - - /** - * @param mixed $value - * @return array - */ - private static function normalizeProductKeyMap(mixed $value): array - { - if (! is_array($value)) { - return []; - } - - $map = []; - - foreach ($value as $tld => $productKey) { - $normalizedTld = ltrim(strtolower(trim((string) $tld)), '.'); - $normalizedProductKey = trim((string) $productKey); - - if ($normalizedTld === '' || $normalizedProductKey === '') { - continue; - } - - $map[$normalizedTld] = $normalizedProductKey; - } - - return $map; - } - - /** - * @return list - */ - private static function livePricedTlds(): array - { - try { - return app(DomainPricingService::class)->getLivePricedTlds(); - } catch (\Throwable) { - return []; - } - } - - /** - * @param list $tlds - * @return array - */ - private static function pricingMapForTlds(array $tlds): array - { - try { - return app(DomainPricingService::class)->getPricingForTlds($tlds); - } catch (\Throwable) { - return []; - } - } -} diff --git a/app/Support/MailboxPricing.php b/app/Support/MailboxPricing.php deleted file mode 100644 index 1dd0176..0000000 --- a/app/Support/MailboxPricing.php +++ /dev/null @@ -1,69 +0,0 @@ - */ - public static function tiers(): array - { - return array_map(fn ($t) => [ - 'mb' => (int) $t['mb'], - 'price_minor' => (int) $t['price_minor'], - 'label' => self::label((int) $t['mb']), - ], (array) config('email.quota_tiers', [])); - } - - public static function isValidQuota(int $mb): bool - { - foreach (self::tiers() as $t) { - if ($t['mb'] === $mb) { - return true; - } - } - - return false; - } - - public static function priceMinorFor(int $mb): int - { - foreach (self::tiers() as $t) { - if ($t['mb'] === $mb) { - return $t['price_minor']; - } - } - - return 0; - } - - /** A tier is free when its monthly price is zero (the 1 GB plan). */ - public static function isFree(int $mb): bool - { - return self::priceMinorFor($mb) === 0; - } - - /** The smallest free tier's quota (the default for new mailboxes). */ - public static function freeQuotaMb(): int - { - foreach (self::tiers() as $t) { - if ($t['price_minor'] === 0) { - return $t['mb']; - } - } - - return self::tiers()[0]['mb'] ?? 1024; - } - - public static function defaultQuotaMb(): int - { - $default = (int) config('email.default_quota_mb', 1024); - - return self::isValidQuota($default) ? $default : self::freeQuotaMb(); - } - - public static function label(int $mb): string - { - return $mb % 1024 === 0 ? ($mb / 1024).' GB' : $mb.' MB'; - } -} diff --git a/app/Support/ResellerClubLegacy.php b/app/Support/ResellerClubLegacy.php deleted file mode 100644 index 44f6f96..0000000 --- a/app/Support/ResellerClubLegacy.php +++ /dev/null @@ -1,122 +0,0 @@ -order_type === RcServiceOrder::TYPE_RENEWAL) { - return true; - } - - return (bool) data_get($order->meta, 'is_renewal', false); - } - - public static function isRenewalDomainOrder(DomainOrder $order): bool - { - return $order->order_type === DomainOrder::TYPE_RENEWAL; - } - - public static function canAutomateFulfillment(RcServiceOrder $order): bool - { - if (! self::integrationEnabled()) { - return false; - } - - if (self::newOrdersEnabled()) { - return true; - } - - return self::renewalsEnabled() && self::isRenewalRcServiceOrder($order); - } - - /** - * @param Collection|iterable $items - */ - public static function assertCartCheckoutAllowed(iterable $items): void - { - $items = $items instanceof Collection ? $items : collect($items); - - if ($items->isEmpty()) { - throw new RuntimeException('Your cart is empty.'); - } - - if (self::newOrdersEnabled()) { - return; - } - - if (! self::renewalsEnabled()) { - throw new RuntimeException(self::renewalsDisabledMessage()); - } - - if ($items->contains(fn (RcServiceOrder $item) => ! self::isRenewalRcServiceOrder($item))) { - throw new RuntimeException( - 'ResellerClub checkout is limited to renewals for existing services. ' - .'New product purchases use our current domain and hosting products instead.' - ); - } - } - - public static function assertNewSaleAllowed(): void - { - if (self::newOrdersEnabled()) { - return; - } - - throw new RuntimeException( - 'ResellerClub is no longer available for new product purchases. ' - .'Use our domain and hosting products instead, or renew an existing ResellerClub service from your dashboard.' - ); - } - - public static function assertRenewalsAllowed(): void - { - if (! self::renewalsEnabled()) { - throw new RuntimeException(self::renewalsDisabledMessage()); - } - } - - /** @deprecated Use assertNewSaleAllowed() */ - public static function assertNewOrdersAllowed(): void - { - self::assertNewSaleAllowed(); - } - - public static function renewalsDisabledMessage(): string - { - return 'ResellerClub renewals are not available at this time. Please contact support if you need assistance.'; - } - - public static function fulfillmentDisabledMessage(): string - { - return 'Automated ResellerClub fulfillment for new purchases is no longer available. ' - .'Open a support ticket if you need help with a legacy order.'; - } -} diff --git a/config/afia.php b/config/afia.php deleted file mode 100644 index 2ac107c..0000000 --- a/config/afia.php +++ /dev/null @@ -1,10 +0,0 @@ - env('AFIA_PRODUCT', 'mini'), - 'enabled' => (bool) env('AFIA_ENABLED', true), - 'provider' => env('AFIA_PROVIDER', 'openai'), // openai | anthropic - 'model' => env('AFIA_MODEL', 'gpt-4o-mini'), - 'api_key' => env('AFIA_API_KEY'), -]; diff --git a/config/android_app_links.php b/config/android_app_links.php deleted file mode 100644 index 7401ae4..0000000 --- a/config/android_app_links.php +++ /dev/null @@ -1,25 +0,0 @@ - env('ANDROID_APP_PACKAGE', 'com.ladill.mini'), - - 'sha256_cert_fingerprints' => array_values(array_filter(array_map( - static fn (string $fingerprint): string => strtoupper(trim($fingerprint)), - explode(',', (string) env('ANDROID_APP_SHA256_FINGERPRINTS', '')), - ))), - -]; diff --git a/config/domain.php b/config/domain.php deleted file mode 100644 index 6e51838..0000000 --- a/config/domain.php +++ /dev/null @@ -1,6 +0,0 @@ - env('DOMAIN_API_URL', 'https://ladill.com/api/domains'), - 'api_key' => env('DOMAIN_API_KEY_HOSTING'), -]; diff --git a/config/email.php b/config/email.php deleted file mode 100644 index de2a4ea..0000000 --- a/config/email.php +++ /dev/null @@ -1,21 +0,0 @@ - env('EMAIL_CURRENCY', 'GHS'), - 'default_quota_mb' => (int) env('EMAIL_DEFAULT_QUOTA_MB', 1024), // new mailboxes start on the free 1 GB tier - - // Storage tiers: quota (MB) → monthly price (minor units). 1 GB is free. - 'quota_tiers' => [ - ['mb' => 1024, 'price_minor' => 0], // 1 GB — Free forever - ['mb' => 5120, 'price_minor' => 1000], // 5 GB — GHS 10 - ['mb' => 10240, 'price_minor' => 2000], // 10 GB — GHS 20 - ['mb' => 25600, 'price_minor' => 3000], // 25 GB — GHS 30 - ['mb' => 51200, 'price_minor' => 6000], // 50 GB — GHS 60 - ], -]; diff --git a/config/emaildomain.php b/config/emaildomain.php deleted file mode 100644 index 69d5cd6..0000000 --- a/config/emaildomain.php +++ /dev/null @@ -1,7 +0,0 @@ - env('EMAILDOMAIN_API_URL', 'https://ladill.com/api/email-domains'), - 'api_key' => env('EMAILDOMAIN_API_KEY_EMAIL'), -]; diff --git a/config/hosting.php b/config/hosting.php deleted file mode 100644 index a0b0375..0000000 --- a/config/hosting.php +++ /dev/null @@ -1,703 +0,0 @@ - [ - 'client_id' => env('CONTABO_CLIENT_ID'), - 'client_secret' => env('CONTABO_CLIENT_SECRET'), - 'api_user' => env('CONTABO_API_USER'), - 'api_password' => env('CONTABO_API_PASSWORD'), - 'product_catalog_endpoint' => env('CONTABO_PRODUCT_CATALOG_ENDPOINT'), - ], - - /* - |-------------------------------------------------------------------------- - | Pricing Configuration - |-------------------------------------------------------------------------- - | - | Dynamic pricing settings for VPS/Dedicated servers. - | Base prices from Contabo are in USD, converted to GHS using exchange rate. - | - */ - 'pricing' => [ - 'base_currency' => 'USD', - 'display_currency' => 'GHS', - - // Fallback rate used when live API fails - 'fallback_usd_to_ghs_rate' => env('FALLBACK_USD_TO_GHS_RATE', 15.50), - - // Profit margins (percentage on top of converted Contabo USD price) - 'margins' => [ - 'vps' => env('VPS_PROFIT_MARGIN', 45), - 'dedicated' => env('DEDICATED_PROFIT_MARGIN', 30), - ], - - 'contabo_price_cache_ttl' => env('CONTABO_PRICE_CACHE_TTL', 3600), - - 'term_discounts' => [ - 'quarterly' => 5, - 'semiannual' => 10, - 'yearly' => 20, - ], - - 'setup_fee_rules' => [ - // VPS 10 only — monthly and semiannual cycles carry a setup fee equal to 1× the monthly price - [ - 'product_ids' => ['V91'], - 'billing_cycles' => ['monthly', 'semiannual'], - 'monthly_price_multiplier' => 1, - 'label' => 'One-time setup fee', - ], - // Dedicated servers — fixed EUR fee that decreases with longer commitment; waived on yearly - [ - 'product_ids' => ['amd-ryzen-12-cores', 'amd-genoa-24-cores'], - 'billing_cycles' => ['monthly', 'quarterly', 'semiannual'], - 'fixed_eur_by_cycle' => [ - 'monthly' => 39.99, - 'quarterly' => 29.99, - 'semiannual' => 19.99, - ], - 'label' => 'One-time setup fee', - ], - ], - - // Fallback Contabo base prices in USD. Live API/feed prices are preferred. - // Keep these values as a fail-safe for API downtime. - 'contabo_base_prices' => [ - 'V91' => ['monthly' => 4.99, 'name' => 'Cloud VPS 10 NVMe', 'cpu' => 3, 'ram_gb' => 8, 'disk_gb' => 75], - 'V94' => ['monthly' => 7.00, 'name' => 'Cloud VPS 20 NVMe', 'cpu' => 6, 'ram_gb' => 12, 'disk_gb' => 100], - 'V97' => ['monthly' => 14.00, 'name' => 'Cloud VPS 30 NVMe', 'cpu' => 8, 'ram_gb' => 24, 'disk_gb' => 200], - 'V100' => ['monthly' => 25.00, 'name' => 'Cloud VPS 40 NVMe', 'cpu' => 12, 'ram_gb' => 48, 'disk_gb' => 250], - 'amd-ryzen-12-cores' => ['monthly' => 104.64, 'name' => 'AMD Ryzen 12 Cores', 'cpu' => 12, 'ram_gb' => 64, 'disk_gb' => 1000], - 'amd-genoa-24-cores' => ['monthly' => 184.21, 'name' => 'AMD Genoa 24 Cores', 'cpu' => 24, 'ram_gb' => 128, 'disk_gb' => 2000], - ], - ], - - /* - |-------------------------------------------------------------------------- - | Server Order Options - |-------------------------------------------------------------------------- - | - | Surcharges shown here are Contabo-side monthly USD add-on costs before - | the Ladill exchange-rate conversion and product margin are applied. - | Update these values whenever Contabo changes its commercial pricing. - | - */ - 'server_order' => [ - 'regions' => [ - 'EU' => ['label' => 'Europe (Germany)', 'monthly_usd' => 0.00, 'automated' => true], - 'US-central' => ['label' => 'US Central', 'monthly_usd' => 0.00, 'automated' => true], - 'US-east' => ['label' => 'US East', 'monthly_usd' => 0.00, 'automated' => true], - 'US-west' => ['label' => 'US West', 'monthly_usd' => 0.00, 'automated' => true], - 'UK' => ['label' => 'United Kingdom', 'monthly_usd' => 0.00, 'automated' => true], - 'SIN' => ['label' => 'Singapore', 'monthly_usd' => 0.00, 'automated' => true], - 'AUS' => ['label' => 'Australia', 'monthly_usd' => 0.00, 'automated' => true], - 'JPN' => ['label' => 'Japan', 'monthly_usd' => 0.00, 'automated' => true], - ], - 'image_pricing_rules' => [ - [ - 'key' => 'windows', - 'label' => 'Windows Server', - 'monthly_usd' => 9.30, - 'match' => ['windows'], - 'os_family' => 'windows', - 'default_user' => 'administrator', - 'requires_custom_image_addon' => false, - 'automated' => true, - ], - [ - 'key' => 'ubuntu', - 'label' => 'Ubuntu', - 'monthly_usd' => 0.00, - 'match' => ['ubuntu'], - 'os_family' => 'linux', - 'default_user' => 'root', - 'requires_custom_image_addon' => false, - 'automated' => true, - ], - [ - 'key' => 'rhel', - 'label' => 'RHEL Variants', - 'monthly_usd' => 0.00, - 'match' => ['alma', 'rocky', 'rhel', 'centos'], - 'os_family' => 'linux', - 'default_user' => 'root', - 'requires_custom_image_addon' => false, - 'automated' => true, - ], - [ - 'key' => 'custom', - 'label' => 'Custom Images', - 'monthly_usd' => 0.00, - 'custom_image' => true, - 'os_family' => 'linux', - 'default_user' => 'root', - 'requires_custom_image_addon' => true, - 'automated' => true, - ], - [ - 'key' => 'linux', - 'label' => 'Linux', - 'monthly_usd' => 0.00, - 'match' => ['debian', 'fedora', 'linux', 'bsd'], - 'os_family' => 'linux', - 'default_user' => 'root', - 'requires_custom_image_addon' => false, - 'automated' => true, - ], - ], - 'fallback_images' => [ - [ - 'value' => 'afecbb85-e2fc-46f0-9684-b46b1faf00bb', - 'label' => 'Ubuntu 22.04', - 'description' => 'Fallback Ubuntu image', - 'os_family' => 'linux', - 'monthly_usd' => 0.00, - 'default_user' => 'root', - 'requires_custom_image_addon' => false, - 'automated' => true, - ], - ], - 'managed_stack_supported_images' => [ - [ - 'value' => 'afecbb85-e2fc-46f0-9684-b46b1faf00bb', - 'label' => 'Ubuntu 22.04 LTS', - 'match' => ['ubuntu 22.04'], - 'os_family' => 'linux', - ], - [ - 'label' => 'Ubuntu 24.04 LTS', - 'match' => ['ubuntu 24.04'], - 'os_family' => 'linux', - ], - [ - 'label' => 'Debian 12', - 'match' => ['debian 12', 'bookworm'], - 'os_family' => 'linux', - ], - [ - 'label' => 'AlmaLinux', - 'match' => ['almalinux', 'alma linux', 'alma'], - 'os_family' => 'linux', - ], - [ - 'label' => 'Rocky Linux', - 'match' => ['rocky linux', 'rocky'], - 'os_family' => 'linux', - ], - [ - 'label' => 'Fedora', - 'match' => ['fedora'], - 'os_family' => 'linux', - ], - [ - 'label' => 'openSUSE Leap', - 'match' => ['opensuse leap', 'open suse leap', 'opensuse', 'leap'], - 'os_family' => 'linux', - ], - [ - 'label' => 'CentOS', - 'match' => ['centos'], - 'os_family' => 'linux', - ], - ], - 'licenses' => [ - 'none' => [ - 'label' => 'Remote Login Only', - 'description' => 'No hosting panel or commercial control-panel license.', - 'monthly_usd' => 0.00, - 'license' => null, - 'panel' => null, - 'automated' => true, - ], - 'ladill_panel' => [ - 'label' => 'Ladill Server Manager', - 'description' => 'Manage power, status, and server details from the Ladill server manager.', - 'monthly_usd' => 0.00, - 'license' => null, - 'panel' => 'ladill', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'requires_managed_stack_image' => true, - 'automated' => true, - ], - 'plesk_host' => [ - 'label' => 'Plesk + Linux', - 'description' => 'Plesk host edition on Linux.', - 'monthly_usd' => 15.00, - 'license' => 'PleskHost', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'plesk_windows' => [ - 'label' => 'Plesk + Windows', - 'description' => 'Plesk on Windows Server.', - 'monthly_usd' => 22.70, - 'license' => 'PleskHost', - 'compatible_os_families' => ['windows'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_5' => [ - 'label' => 'cPanel 5', - 'description' => 'cPanel license for up to 5 accounts.', - 'monthly_usd' => 35.99, - 'license' => 'cPanel5', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_30' => [ - 'label' => 'cPanel 30', - 'description' => 'cPanel license for up to 30 accounts.', - 'monthly_usd' => 53.99, - 'license' => 'cPanel30', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_50' => [ - 'label' => 'cPanel 50', - 'description' => 'cPanel license for up to 50 accounts.', - 'monthly_usd' => 53.99 + (0.49 * 20), - 'license' => 'cPanel50', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_100' => [ - 'label' => 'cPanel 100', - 'description' => 'cPanel license for up to 100 accounts.', - 'monthly_usd' => 69.99, - 'license' => 'cPanel100', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_150' => [ - 'label' => 'cPanel 150', - 'description' => 'cPanel license for up to 150 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 50), - 'license' => 'cPanel150', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_200' => [ - 'label' => 'cPanel 200', - 'description' => 'cPanel license for up to 200 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 100), - 'license' => 'cPanel200', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_250' => [ - 'label' => 'cPanel 250', - 'description' => 'cPanel license for up to 250 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 150), - 'license' => 'cPanel250', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_300' => [ - 'label' => 'cPanel 300', - 'description' => 'cPanel license for up to 300 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 200), - 'license' => 'cPanel300', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_350' => [ - 'label' => 'cPanel 350', - 'description' => 'cPanel license for up to 350 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 250), - 'license' => 'cPanel350', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_400' => [ - 'label' => 'cPanel 400', - 'description' => 'cPanel license for up to 400 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 300), - 'license' => 'cPanel400', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_450' => [ - 'label' => 'cPanel 450', - 'description' => 'cPanel license for up to 450 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 350), - 'license' => 'cPanel450', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_500' => [ - 'label' => 'cPanel 500', - 'description' => 'cPanel license for up to 500 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 400), - 'license' => 'cPanel500', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_550' => [ - 'label' => 'cPanel 550', - 'description' => 'cPanel license for up to 550 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 450), - 'license' => 'cPanel550', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_600' => [ - 'label' => 'cPanel 600', - 'description' => 'cPanel license for up to 600 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 500), - 'license' => 'cPanel600', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_650' => [ - 'label' => 'cPanel 650', - 'description' => 'cPanel license for up to 650 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 550), - 'license' => 'cPanel650', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_700' => [ - 'label' => 'cPanel 700', - 'description' => 'cPanel license for up to 700 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 600), - 'license' => 'cPanel700', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_750' => [ - 'label' => 'cPanel 750', - 'description' => 'cPanel license for up to 750 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 650), - 'license' => 'cPanel750', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_800' => [ - 'label' => 'cPanel 800', - 'description' => 'cPanel license for up to 800 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 700), - 'license' => 'cPanel800', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_850' => [ - 'label' => 'cPanel 850', - 'description' => 'cPanel license for up to 850 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 750), - 'license' => 'cPanel850', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_900' => [ - 'label' => 'cPanel 900', - 'description' => 'cPanel license for up to 900 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 800), - 'license' => 'cPanel900', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_950' => [ - 'label' => 'cPanel 950', - 'description' => 'cPanel license for up to 950 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 850), - 'license' => 'cPanel950', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'cpanel_1000' => [ - 'label' => 'cPanel 1000', - 'description' => 'cPanel license for up to 1000 accounts.', - 'monthly_usd' => 69.99 + (0.49 * 900), - 'license' => 'cPanel1000', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - ], - 'applications' => [ - 'none' => [ - 'label' => 'No Preinstalled App', - 'description' => 'Provision the server without an extra preinstalled application.', - 'monthly_usd' => 0.00, - 'automated' => true, - ], - 'webmin' => [ - 'label' => 'Webmin', - 'description' => 'Free Webmin server panel installed automatically on Linux.', - 'monthly_usd' => 0.00, - 'cloud_init_preset' => 'webmin', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'webmin_lamp' => [ - 'label' => 'Webmin + LAMP', - 'description' => 'Free Webmin plus Apache, MariaDB, and PHP on Linux.', - 'monthly_usd' => 0.00, - 'cloud_init_preset' => 'webmin_lamp', - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'ipfs_node' => [ - 'label' => 'IPFS Node', - 'description' => 'Contabo application profile for IPFS nodes.', - 'monthly_usd' => 0.00, - 'application_id' => env('CONTABO_APP_ID_IPFS_NODE'), - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'flux_node' => [ - 'label' => 'Flux Node', - 'description' => 'Contabo application profile for Flux nodes.', - 'monthly_usd' => 0.00, - 'application_id' => env('CONTABO_APP_ID_FLUX_NODE'), - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'horizon_node' => [ - 'label' => 'Horizen Node', - 'description' => 'Contabo application profile for Horizen nodes.', - 'monthly_usd' => 0.00, - 'application_id' => env('CONTABO_APP_ID_HORIZON_NODE'), - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'ethereum_node' => [ - 'label' => 'Ethereum Node', - 'description' => 'Contabo application profile for Ethereum 2.0 nodes.', - 'monthly_usd' => 0.00, - 'application_id' => null, - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - 'bitcoin_node' => [ - 'label' => 'Bitcoin Full Node', - 'description' => 'Contabo application profile for Bitcoin full nodes.', - 'monthly_usd' => 0.00, - 'application_id' => null, - 'compatible_os_families' => ['linux'], - 'requires_image' => true, - 'automated' => true, - ], - ], - 'additional_ip' => [ - 'none' => [ - 'label' => '1 IP Address', - 'description' => 'Default primary IP only.', - 'monthly_usd' => 0.00, - 'add_on' => null, - 'automated' => true, - ], - 'one_extra' => [ - 'label' => '1 Additional IP', - 'description' => 'Adds one extra IPv4 address.', - 'monthly_usd' => 4.50, - 'add_on' => 'additionalIps', - 'automated' => true, - ], - ], - 'private_networking' => [ - 'disabled' => [ - 'label' => 'No Private Networking', - 'description' => 'Do not enable the private networking add-on.', - 'monthly_usd' => 0.00, - 'add_on' => null, - 'automated' => true, - ], - 'enabled' => [ - 'label' => 'Private Networking Enabled', - 'description' => 'Purchases the private networking add-on.', - 'monthly_usd' => 2.99, - 'add_on' => 'privateNetworking', - 'automated' => true, - ], - ], - 'storage_types' => [ - 'included' => [ - 'label' => 'Included Storage', - 'description' => 'Use the plan default storage.', - 'monthly_usd' => 0.00, - 'add_on' => null, - 'automated' => true, - ], - 'ssd_300' => [ - 'label' => '300 GB SSD', - 'description' => 'Higher SSD storage tier.', - 'monthly_usd' => 1.95, - 'add_on' => 'extraStorage', - 'automated' => false, - ], - 'nvme_150' => [ - 'label' => '150 GB NVMe', - 'description' => 'Higher NVMe storage tier.', - 'monthly_usd' => 2.30, - 'add_on' => 'extraStorage', - 'automated' => false, - ], - ], - 'object_storage' => [ - 'none' => [ - 'label' => 'No Object Storage', - 'description' => 'Do not order object storage with this server.', - 'monthly_usd' => 0.00, - 'automated' => false, - ], - 'eu_250' => [ - 'label' => '250 GB Object Storage (EU)', - 'description' => 'S3-compatible object storage in the European Union.', - 'monthly_usd' => 2.99, - 'automated' => false, - ], - 'eu_500' => [ - 'label' => '500 GB Object Storage (EU)', - 'description' => 'S3-compatible object storage in the European Union.', - 'monthly_usd' => 5.98, - 'automated' => false, - ], - 'eu_750' => [ - 'label' => '750 GB Object Storage (EU)', - 'description' => 'S3-compatible object storage in the European Union.', - 'monthly_usd' => 8.97, - 'automated' => false, - ], - 'eu_1024' => [ - 'label' => '1 TB Object Storage (EU)', - 'description' => 'S3-compatible object storage in the European Union.', - 'monthly_usd' => 11.96, - 'automated' => false, - ], - ], - 'linux_default_users' => [ - 'root' => ['label' => 'root'], - 'admin' => ['label' => 'admin'], - ], - 'windows_default_users' => [ - 'admin' => ['label' => 'admin'], - 'administrator' => ['label' => 'administrator'], - ], - ], - - 'server_agent' => [ - 'release_version' => env('SERVER_AGENT_RELEASE_VERSION', '0.2.0'), - 'signed_release_ttl_minutes' => (int) env('SERVER_AGENT_SIGNED_RELEASE_TTL_MINUTES', 10080), - 'heartbeat_interval_seconds' => 15, - ], - - /* - |-------------------------------------------------------------------------- - | Shared Hosting Configuration - |-------------------------------------------------------------------------- - | - | Settings for the shared hosting nodes. - | - */ - 'shared' => [ - 'default_php_version' => '8.2', - 'available_php_versions' => ['8.0', '8.1', '8.2', '8.3'], - 'default_document_root' => 'public_html', - 'max_upload_size_mb' => 64, - 'max_execution_time' => 300, - 'memory_limit_mb' => 256, - 'phpmyadmin_url' => env('HOSTING_PHPMYADMIN_URL', ''), - 'phpmyadmin_sso_secret' => env('HOSTING_PHPMYADMIN_SSO_SECRET', ''), - ], - - /* - |-------------------------------------------------------------------------- - | VPS Configuration - |-------------------------------------------------------------------------- - | - | Default settings for VPS instances. - | - */ - 'vps' => [ - 'default_region' => 'EU', - 'default_image' => 'afecbb85-e2fc-46f0-9684-b46b1faf00bb', // Ubuntu 22.04 - 'available_regions' => ['EU', 'US-central', 'US-east', 'US-west', 'SIN', 'UK', 'AUS', 'JPN'], - ], - - /* - |-------------------------------------------------------------------------- - | App Installer Configuration - |-------------------------------------------------------------------------- - | - | Settings for the one-click app installer. - | - */ - 'apps' => [ - 'enabled' => ['wordpress', 'joomla', 'drupal', 'opencart'], - 'magento_enabled' => false, // Requires higher resource plans - 'wordpress' => [ - 'default_version' => '6.4', - 'wp_cli_path' => '/usr/local/bin/wp', - ], - ], - - /* - |-------------------------------------------------------------------------- - | Provisioning Settings - |-------------------------------------------------------------------------- - | - | General provisioning configuration. - | - */ - 'provisioning' => [ - 'max_retries' => 3, - 'retry_delay_minutes' => 5, - 'timeout_minutes' => 30, - ], - - /* - |-------------------------------------------------------------------------- - | Legacy ResellerClub Integration - |-------------------------------------------------------------------------- - | - | Keep RC integration active for existing customers. - | - */ - 'legacy' => [ - // Keep RC data/API available for customers with existing ResellerClub services. - 'rc_enabled' => (bool) env('LADILL_RC_LEGACY_ENABLED', true), - // New sales and automated fulfillment use Ladill (Dynadot domains, native hosting). - 'rc_new_orders_enabled' => (bool) env('LADILL_RC_NEW_ORDERS_ENABLED', false), - // Renew existing ResellerClub services (hosting, domains, VPS, etc.). - 'rc_renewals_enabled' => (bool) env('LADILL_RC_RENEWALS_ENABLED', true), - ], -]; diff --git a/config/mail_brands.php b/config/mail_brands.php deleted file mode 100644 index c0fa4a7..0000000 --- a/config/mail_brands.php +++ /dev/null @@ -1,123 +0,0 @@ - env( - 'LADILL_ACCOUNT_URL', - 'https://'.env('ACCOUNT_DOMAIN', 'account.'.$platformHost) - ), - - 'brands' => [ - 'ladill' => [ - 'name' => 'Ladill', - 'logo' => 'ladill-logo-white.png', - 'logo_class' => 'email-logo', - 'app_url' => env('APP_URL', 'https://ladill.com'), - 'footer_account' => 'an account with Ladill', - 'dashboard_path' => '/dashboard', - 'support_path' => '/support', - 'home_label' => 'ladill.com', - ], - 'hosting' => [ - 'name' => 'Ladill Hosting', - 'logo' => 'ladillhosting-logo-email.png', - 'logo_class' => 'email-logo', - 'app_url' => env('LADILL_HOSTING_APP_URL', 'https://hosting.ladill.com'), - 'footer_account' => 'a Ladill Hosting account', - 'dashboard_path' => '/', - 'support_path' => '/support', - 'home_label' => 'hosting.ladill.com', - ], - 'domains' => [ - 'name' => 'Ladill Domains', - 'logo' => 'ladilldomains-logo-email.png', - 'logo_class' => 'email-logo', - 'app_url' => env('LADILL_DOMAINS_APP_URL', 'https://domains.ladill.com'), - 'footer_account' => 'a Ladill Domains account', - 'dashboard_path' => '/', - 'support_path' => '/support', - 'home_label' => 'domains.ladill.com', - ], - 'bird' => [ - 'name' => 'Ladill Bird', - 'logo' => 'ladillbird-logo-email.png', - 'logo_class' => 'email-logo', - 'app_url' => env('LADILL_BIRD_APP_URL', 'https://bird.ladill.com'), - 'footer_account' => 'a Ladill Bird account', - 'dashboard_path' => '/', - 'support_path' => '/support', - 'home_label' => 'bird.ladill.com', - ], - 'mail' => [ - 'name' => 'Ladill Mail', - 'logo' => 'ladillmail-logo-email.png', - 'logo_class' => 'email-logo', - 'app_url' => env('LADILL_WEBMAIL_URL', 'https://mail.ladill.com'), - 'footer_account' => 'a Ladill Mail account', - 'dashboard_path' => '/', - 'support_path' => '/support', - 'home_label' => 'mail.ladill.com', - ], - 'email' => [ - 'name' => 'Ladill Email', - 'logo' => 'ladillemail-logo-email.png', - 'logo_class' => 'email-logo', - 'app_url' => env('LADILL_EMAIL_APP_URL', 'https://email.ladill.com'), - 'footer_account' => 'a Ladill Email account', - 'dashboard_path' => '/', - 'support_path' => '/support', - 'home_label' => 'email.ladill.com', - ], - 'qrplus' => [ - 'name' => 'Ladill QR Plus', - 'logo' => 'ladillqrplus-logo-email.png', - 'logo_class' => 'email-logo', - 'app_url' => env('LADILL_QR_APP_URL', 'https://qrplus.ladill.com'), - 'footer_account' => 'a Ladill QR Plus account', - 'dashboard_path' => '/', - 'support_path' => '/support', - 'home_label' => 'qrplus.ladill.com', - ], - 'events' => [ - 'name' => 'Ladill Events', - 'logo' => 'ladillevents-logo-email.png', - 'logo_class' => 'email-logo', - 'app_url' => env('LADILL_EVENTS_APP_URL', 'https://events.ladill.com'), - 'footer_account' => 'a Ladill Events account', - 'dashboard_path' => '/', - 'support_path' => '/support', - 'home_label' => 'events.ladill.com', - ], - 'transfer' => [ - 'name' => 'Ladill Transfer', - 'logo' => 'ladilltransfer-logo-email.png', - 'logo_class' => 'email-logo email-logo-transfer', - 'app_url' => env('LADILL_TRANSFER_APP_URL', 'https://transfer.ladill.com'), - 'footer_account' => 'a Ladill Transfer account', - 'dashboard_path' => '/', - 'support_path' => '/support', - 'home_label' => 'transfer.ladill.com', - ], - 'mini' => [ - 'name' => 'Ladill Mini', - 'logo' => 'ladillmini-logo-email.png', - 'logo_class' => 'email-logo', - 'app_url' => env('LADILL_MINI_APP_URL', 'https://mini.ladill.com'), - 'footer_account' => 'a Ladill Mini account', - 'dashboard_path' => '/', - 'support_path' => '/support', - 'home_label' => 'mini.ladill.com', - ], - 'servers' => [ - 'name' => 'Ladill Servers', - 'logo' => 'ladillservers-logo-email.png', - 'logo_class' => 'email-logo', - 'app_url' => env('LADILL_SERVERS_APP_URL', 'https://servers.ladill.com'), - 'footer_account' => 'a Ladill Servers account', - 'dashboard_path' => '/', - 'support_path' => '/support', - 'home_label' => 'servers.ladill.com', - ], - ], -]; diff --git a/config/mailbox.php b/config/mailbox.php deleted file mode 100644 index 14a403c..0000000 --- a/config/mailbox.php +++ /dev/null @@ -1,9 +0,0 @@ - env('MAILBOX_API_URL', 'https://ladill.com/api/mailboxes'), - 'api_key' => env('MAILBOX_API_KEY_EMAIL'), -]; diff --git a/config/notifications.php b/config/notifications.php deleted file mode 100644 index 82c6f9b..0000000 --- a/config/notifications.php +++ /dev/null @@ -1,17 +0,0 @@ - (int) env('NOTIFICATIONS_FCM_ACTIVE_USER_DAYS', 60), - -]; diff --git a/config/qr.php b/config/qr.php deleted file mode 100644 index 50706a9..0000000 --- a/config/qr.php +++ /dev/null @@ -1,9 +0,0 @@ - (float) env('QR_PRICE_PER_QR_GHS', 5.0), - 'min_topup_ghs' => (float) env('QR_MIN_TOPUP_GHS', 5.0), - 'max_pdf_bytes' => (int) env('QR_MAX_PDF_BYTES', 104857600), // 100 MB - 'short_code_length' => (int) env('QR_SHORT_CODE_LENGTH', 8), - 'scan_unique_window_hours' => (int) env('QR_SCAN_UNIQUE_WINDOW_HOURS', 24), -]; diff --git a/public/favicon.ico b/public/favicon.ico index c2b46bd6eb23cc028d07bd9cfa55afa975cdf16d..2a03e3f3652e587a107335b9bfef0730c296c9b7 100644 GIT binary patch literal 270398 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 O;T+%F)yeUpkN*Dy+#p8) literal 4414 zcmd^@%}*0S7{=HCKrb5gf-x#4UQCP^O~AyP9!yNU7%s+xhM1VBc+jKan}vcvOO=lU zp#f4N35gO3Bo+dag4G%@7HbO94_YW~>BqL+$DOUa7Q5SaJ4dH^cQ&&#^LyUenRjQ( zFb9NRSs6pFO!*;(Im|FjxnM#OQz;lJ7t_H^mVHg8d2lIZOY=OmBwk~E(syOo9!}Lo z6pfup)wDxv{ zW@o{t)2cd3@jtS(q|8&%5f~c-pGqle(8`ZF9GckDxCAFBsrfv|!Q0bQ5})Wutgq*2 z(Bgk&dAVdZ;Ri-XsrZ4B5h}jBtqti|Y@Y@#{(Cw*>G%ropjbXx>(X4J$)l^Qvi~Gr z70nmwCN?)^BkO<9Zr5xvrxTpVlQFNiXud4^&rQf;p&k!kUth_5+OUuyuGM*nIa9*%o`x;HTv| zxTs%@>0Z5_N<-Y`%HoG{qv@T%t;N&UjQz`(v@E_pji74=5v#RizTadLXOcAkZ#BJ< zxrO+m4*gLCe!PW$Xh^dLPe%thpHJprZCuD<6Afzq%j59sB%+@0@b&i>uR-`GleH_Q zD=&BOkY!8a6CIBC)kw$V*qN9psKGZdAg+6fP4b&<9~9Ze_)qu?hT}-agJPGQ+l}DV z6nwqCaO-rU9RAf#=ot*Aho9C-LuY zOP`{KLh*;peVVZ+nh5`Vy$g@n#vBcW_;&L(q*I)lYekhy@&8i8S3G7L_G!q+x0x!0 z?_ef|7X?zOB=N}^@It*4_gW0H1~op3xv4~0rAKYW+5F5oG>nP{8*Vp`phm}HUVjW9 mjVJJF=rWv^XNYWl(FQ0`yU_W&lrwb! - - - - - - - - - - \ No newline at end of file diff --git a/resources/views/auth/signed-out.blade.php b/resources/views/auth/signed-out.blade.php index c6306cc..205c73b 100644 --- a/resources/views/auth/signed-out.blade.php +++ b/resources/views/auth/signed-out.blade.php @@ -1,6 +1,6 @@ @php $signedOut = (array) config('signed_out'); - $logo = (string) ($signedOut['logo'] ?? 'images/logo/ladillgive-logo.svg'); + $logo = (string) ($signedOut['logo'] ?? 'images/logo/ladillpos-logo.svg'); $logoPath = public_path($logo); @endphp diff --git a/resources/views/components/btn/create.blade.php b/resources/views/components/btn/create.blade.php deleted file mode 100644 index caa7d06..0000000 --- a/resources/views/components/btn/create.blade.php +++ /dev/null @@ -1,17 +0,0 @@ -@props([ - 'href' => null, - 'type' => 'button', -]) - -@php - $tag = $href ? 'a' : 'button'; -@endphp - -<{{ $tag }} - @if ($href) href="{{ $href }}" @endif - @if ($tag === 'button') type="{{ $type }}" @endif - {{ $attributes->class(['btn-primary']) }} -> - - {{ $slot }} - diff --git a/resources/views/components/btn/primary.blade.php b/resources/views/components/btn/primary.blade.php deleted file mode 100644 index b9cb905..0000000 --- a/resources/views/components/btn/primary.blade.php +++ /dev/null @@ -1,16 +0,0 @@ -@props([ - 'href' => null, - 'type' => 'button', -]) - -@php - $tag = $href ? 'a' : 'button'; -@endphp - -<{{ $tag }} - @if ($href) href="{{ $href }}" @endif - @if ($tag === 'button') type="{{ $type }}" @endif - {{ $attributes->class(['btn-primary']) }} -> - {{ $slot }} - diff --git a/resources/views/components/confirm-dialog.blade.php b/resources/views/components/confirm-dialog.blade.php deleted file mode 100644 index 1d0ee69..0000000 --- a/resources/views/components/confirm-dialog.blade.php +++ /dev/null @@ -1,69 +0,0 @@ -@props([ - 'name', - 'title', - 'message' => null, - 'action', - 'method' => 'POST', - 'confirmLabel' => 'Confirm', - 'cancelLabel' => 'Cancel', - 'variant' => 'danger', -]) - -@php - $confirmBtnClass = $variant === 'danger' - ? 'bg-red-600 hover:bg-red-700' - : 'bg-violet-600 hover:bg-violet-700'; - $iconWrapClass = $variant === 'danger' - ? 'bg-red-100 text-red-600' - : 'bg-violet-100 text-violet-600'; -@endphp - -@if(isset($trigger)) - - {{ $trigger }} - -@endif - - -
-
-
- @if($variant === 'danger') - - - - @else - - - - @endif -
-

{{ $title }}

- @if($message) -

{{ $message }}

- @endif - @isset($details) -
{{ $details }}
- @endisset -
- -
- @csrf - @if(strtoupper($method) !== 'POST') - @method($method) - @endif - @isset($fields) - {{ $fields }} - @endisset - - -
-
-
diff --git a/resources/views/components/hosting-panel-layout.blade.php b/resources/views/components/hosting-panel-layout.blade.php deleted file mode 100644 index 978ac9e..0000000 --- a/resources/views/components/hosting-panel-layout.blade.php +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - @include('partials.favicon') - {{ $title ?? 'Hosting Panel' }} - Ladill - - - @vite(['resources/css/app.css', 'resources/js/app.js']) - -@php - $canViewAccountDetails = auth()->check() && auth()->user()->can('viewAccount', $account); - $exitUrl = $canViewAccountDetails - ? route('hosting.accounts.show', $account) - : route('hosting.single-domain'); -@endphp - -
- {{-- Mobile sidebar overlay --}} -
- - {{-- Sidebar --}} - - - {{-- Main content --}} -
- {{-- Top bar --}} -
- -
-

{{ $header ?? 'Hosting Panel' }}

- @unless ($canViewAccountDetails) -

Developer access

- @endunless -
- - - Exit Panel - -
- - {{-- Flash messages --}} - @include('partials.flash') - - {{-- Expired Account Banner --}} - @if ($account->isExpired() && $account->isInGracePeriod()) -
-
- -

Account expired. Your site is offline. Files are preserved until {{ $account->gracePeriodEndsAt()->format('M d, Y') }}. - Renew now -

-
-
- @endif - - {{-- Page content --}} -
- {{ $slot }} -
-
-
- - @stack('scripts') - - diff --git a/resources/views/components/icons/multi-domain-hosting.blade.php b/resources/views/components/icons/multi-domain-hosting.blade.php deleted file mode 100644 index 43890e5..0000000 --- a/resources/views/components/icons/multi-domain-hosting.blade.php +++ /dev/null @@ -1,6 +0,0 @@ -@php - $class = $class ?? 'h-5 w-5'; -@endphp - diff --git a/resources/views/components/icons/qr-code.blade.php b/resources/views/components/icons/qr-code.blade.php deleted file mode 100644 index 3cdcc63..0000000 --- a/resources/views/components/icons/qr-code.blade.php +++ /dev/null @@ -1,5 +0,0 @@ -@props(['class' => 'h-5 w-5']) - -merge(['class' => $class]) }} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> - - diff --git a/resources/views/components/icons/single-domain-hosting.blade.php b/resources/views/components/icons/single-domain-hosting.blade.php deleted file mode 100644 index 69304cd..0000000 --- a/resources/views/components/icons/single-domain-hosting.blade.php +++ /dev/null @@ -1,7 +0,0 @@ -@php - $class = $class ?? 'h-5 w-5'; - $strokeWidth = $strokeWidth ?? '1.5'; -@endphp - diff --git a/resources/views/components/icons/unlimited.blade.php b/resources/views/components/icons/unlimited.blade.php deleted file mode 100644 index e7553f4..0000000 --- a/resources/views/components/icons/unlimited.blade.php +++ /dev/null @@ -1,6 +0,0 @@ -@php - $class = $class ?? 'h-5 w-5'; -@endphp - diff --git a/resources/views/components/input-error.blade.php b/resources/views/components/input-error.blade.php deleted file mode 100644 index 9e6da21..0000000 --- a/resources/views/components/input-error.blade.php +++ /dev/null @@ -1,9 +0,0 @@ -@props(['messages']) - -@if ($messages) -
    merge(['class' => 'text-sm text-red-600 space-y-1']) }}> - @foreach ((array) $messages as $message) -
  • {{ $message }}
  • - @endforeach -
-@endif diff --git a/resources/views/components/mobile-page-header.blade.php b/resources/views/components/mobile-page-header.blade.php deleted file mode 100644 index dc4aa60..0000000 --- a/resources/views/components/mobile-page-header.blade.php +++ /dev/null @@ -1,32 +0,0 @@ -@props([ - 'title', - 'subtitle' => null, - 'backUrl' => null, - 'badge' => null, - 'hideAfia' => false, -]) - -
-
- @if ($backUrl) - - - - @endif -
- @if ($subtitle) -

{{ $subtitle }}

- @endif -

{{ $title }}

-
- @if ($badge) - {{ $badge }} - @endif - @unless ($hideAfia) - @include('partials.afia-button', ['compact' => true]) - @endunless - {{ $actions ?? '' }} -
- {{ $slot }} -
diff --git a/resources/views/components/modal.blade.php b/resources/views/components/modal.blade.php deleted file mode 100644 index a823707..0000000 --- a/resources/views/components/modal.blade.php +++ /dev/null @@ -1,88 +0,0 @@ -@props([ - 'name', - 'show' => false, - 'maxWidth' => '2xl' -]) - -@php -$maxWidth = [ - 'sm' => 'sm:max-w-sm', - 'md' => 'sm:max-w-md', - 'lg' => 'sm:max-w-lg', - 'xl' => 'sm:max-w-xl', - '2xl' => 'sm:max-w-2xl', -][$maxWidth]; -@endphp - -
- {{-- Backdrop click to close --}} -
- - {{-- Panel: bottom-sheet on mobile, centered card on sm+ --}} -
- {{-- Drag handle (mobile only) --}} -
-
-
- - {{-- Close button (desktop only) --}} - - - {{ $slot }} -
-
diff --git a/resources/views/components/user-layout.blade.php b/resources/views/components/user-layout.blade.php deleted file mode 100644 index 96a7909..0000000 --- a/resources/views/components/user-layout.blade.php +++ /dev/null @@ -1,33 +0,0 @@ -@props(['title' => 'Ladill Hosting']) - - - - - - - {{ $title ?? 'Ladill Hosting' }} - @include('partials.favicon') - - - @vite(['resources/css/app.css', 'resources/js/app.js']) - - -
-
- -
- @include('partials.topbar') -
- @include('partials.flash') - {{ $slot }} -
-
-
- @auth - @include('partials.afia') - @endauth - - diff --git a/resources/views/components/user/service-topup-modal.blade.php b/resources/views/components/user/service-topup-modal.blade.php deleted file mode 100644 index f9a5675..0000000 --- a/resources/views/components/user/service-topup-modal.blade.php +++ /dev/null @@ -1,124 +0,0 @@ -@props([ - 'id', - 'title' => 'Add credits', - 'description' => '', - 'topupAction', - 'minTopup' => 5, - 'suggestedAmount' => 10, - 'ladillWalletBalance' => 0, - 'serviceBalance' => null, - 'serviceBalanceLabel' => 'Current balance', - 'openOnLoad' => false, - 'returnUrl' => null, -]) - -@php - // Single-wallet (siloing step 2): there is one Ladill wallet, so "pay from - // wallet into service credits" is a meaningless round-trip — top up the one - // wallet directly (Paystack). Transfer option removed. - $canPayFromWallet = false; -@endphp - - -
-
-

{{ $title }}

- @if($description) -

{{ $description }}

- @endif -
- -
- @csrf - @if($returnUrl) - - @endif - - @if($serviceBalance !== null) -
-

GHS {{ number_format((float) $serviceBalance, 2) }}

-

{{ $serviceBalanceLabel }}

-
- @endif - -
- -
- - -

Minimum GHS {{ number_format($minTopup, 2) }}

-
- - - - @if($canPayFromWallet) -
-

Payment method

-
- - -
-
- @else -

Pay with Paystack.

- @endif - -
- - -
- - @include('partials.paystack-sheet') -
-
-
diff --git a/resources/views/email/account/billing.blade.php b/resources/views/email/account/billing.blade.php deleted file mode 100644 index 2f7442a..0000000 --- a/resources/views/email/account/billing.blade.php +++ /dev/null @@ -1,52 +0,0 @@ -@extends('layouts.email') -@section('title', 'Billing — Ladill Email') -@section('content') -@php $fmt = fn ($m) => 'GHS '.number_format($m / 100, 2); @endphp -
-

Billing

-

Your Ladill wallet funds mailboxes across every Ladill app.

- -
-
-

Wallet balance

-

{{ $fmt($balanceMinor) }}

- Add funds -
-
-

Spent on email

-

{{ $fmt($spentMinor) }}

-
-
-

Refunded / credited

-

{{ $fmt($creditedMinor) }}

-
-
- -
-
-

Mailbox subscriptions

- priced by storage plan -
- @if(empty($paidMailboxes)) -

No paid mailboxes yet — your free allowance covers you.

- @else -
    - @foreach($paidMailboxes as $m) -
  • -
    -

    {{ $m['address'] ?? '—' }}

    -

    {{ $m['quota_label'] }} · {{ ucfirst($m['status'] ?? 'active') }}

    -
    - {{ $fmt($m['price_minor']) }}/mo -
  • - @endforeach -
-
- Monthly total - {{ $fmt($monthlyTotalMinor) }} -
- @endif -
-

Mailboxes beyond your free allowance renew monthly from your wallet. Keep it funded to avoid interruption.

-
-@endsection diff --git a/resources/views/email/account/developers.blade.php b/resources/views/email/account/developers.blade.php deleted file mode 100644 index d7fa0cd..0000000 --- a/resources/views/email/account/developers.blade.php +++ /dev/null @@ -1,78 +0,0 @@ -@extends('layouts.email') -@section('title', 'Developers — Ladill Email') -@section('content') -
-

Developers

-

API tokens to manage your mailboxes programmatically.

- - @if($newToken) -
-

Your new token — copy it now

-

This is the only time it will be shown.

-
- {{ $newToken }} - -
-
- @endif - - {{-- Create --}} -
-

Create a token

-
- @csrf -
- - - @error('name')

{{ $message }}

@enderror -
- -
-
- - {{-- Tokens --}} -
-

Your tokens

- @forelse($tokens as $token) -
-
-

{{ $token->name }}

-

- Created {{ $token->created_at->diffForHumans() }} · - {{ $token->last_used_at ? 'last used '.$token->last_used_at->diffForHumans() : 'never used' }} -

-
- - - - - -
- @empty -

No tokens yet.

- @endforelse -
- - {{-- Docs --}} -
-

Quick start

-

Authenticate with a Bearer token. Base URL:

- {{ $apiBase }} -
curl {{ $apiBase }}/mailboxes \
-  -H "Authorization: Bearer <your-token>" \
-  -H "Accept: application/json"
-

Endpoints: GET /me, GET /mailboxes. More coming soon.

-
-
-@endsection diff --git a/resources/views/email/account/settings.blade.php b/resources/views/email/account/settings.blade.php deleted file mode 100644 index 8d4e4b1..0000000 --- a/resources/views/email/account/settings.blade.php +++ /dev/null @@ -1,110 +0,0 @@ -@extends('layouts.email') -@section('title', 'Settings — Ladill Email') -@section('content') -
-

Settings

-

Defaults, preferences, and account mailbox linking.

- - @if($showMailboxLinkUi ?? (($linkStatus['linked_mailbox'] ?? null) || ($linkStatus['show_reminder'] ?? false))) -
-
- - @include('partials.ladill-pro-icon') - -
-

Link to mailbox

-

- Your Ladill account uses {{ $linkStatus['account_email'] ?? $account->email }}. - Link a mailbox so Ladill Mail opens with your Ladill sign-in. -

- - @if($linkStatus['linked_mailbox'] ?? null) -
- Linked mailbox: {{ $linkStatus['linked_mailbox'] }} -
- - - - - - @elseif(($linkStatus['stage'] ?? '') === 'needs_domain') -

Add and verify an email domain first.

- - Go to domains - - - @elseif(($linkStatus['stage'] ?? '') === 'needs_mailbox') -

Create a mailbox on your verified domain first.

- - Create mailbox - - - @elseif(count($mailboxOptions) > 0) -
- @csrf @method('PUT') -
- - - @error('mailbox_address')

{{ $message }}

@enderror -
- -
- @endif -
-
-
- @endif - -
- @csrf @method('PUT') - -
-

Mailbox defaults

-
-
- - -
-
- - -
-
-
- -
- -
- - -
-
-@endsection diff --git a/resources/views/email/account/team.blade.php b/resources/views/email/account/team.blade.php deleted file mode 100644 index c1baeaa..0000000 --- a/resources/views/email/account/team.blade.php +++ /dev/null @@ -1,90 +0,0 @@ -@extends('layouts.email') -@section('title', 'Team — Ladill Email') -@section('content') -
-

Team

-

Invite people to help manage this account’s mailboxes & domains.

- - @if($canManage) -
-

Invite a teammate

-
- @csrf -
- - - @error('email')

{{ $message }}

@enderror -
-
- - -
- -
-

Admins can manage mailboxes and the team. Members can manage mailboxes. Invitees join by signing in with that email.

-
- @endif - -
-

Members

-
    -
  • -
    - {{ strtoupper(substr($account->name ?? $account->email, 0, 1)) }} -
    -

    {{ $account->name ?? $account->email }} (you)

    -

    {{ $account->email }}

    -
    -
    - Owner -
  • - - @forelse($members as $member) -
  • -
    - {{ strtoupper(substr($member->email, 0, 1)) }} -
    -

    {{ $member->member->name ?? $member->email }}

    -

    {{ $member->email }}

    -
    -
    -
    - @if($member->status === 'invited') - Invited - @endif - @if($canManage) -
    - @csrf @method('PATCH') - -
    - - - - - - @else - {{ $member->role }} - @endif -
    -
  • - @empty -
  • No teammates yet.
  • - @endforelse -
-
-
-@endsection diff --git a/resources/views/email/account/wallet.blade.php b/resources/views/email/account/wallet.blade.php deleted file mode 100644 index 59a772b..0000000 --- a/resources/views/email/account/wallet.blade.php +++ /dev/null @@ -1,27 +0,0 @@ -@extends('layouts.email') -@section('title', 'Wallet — Ladill Email') -@section('content') -@php $fmt = fn ($m) => 'GHS '.number_format($m / 100, 2); @endphp -
-

Wallet

-

Your Ladill wallet funds mailboxes across every Ladill app.

- -
-

Balance

-

{{ $fmt($balanceMinor) }}

- Add funds -
- -
-
-

Spent on email

-

{{ $fmt($spentMinor) }}

-
-
-

Refunded / credited

-

{{ $fmt($creditedMinor) }}

-
-
-

Mailboxes beyond your free allowance are billed monthly from this wallet.

-
-@endsection diff --git a/resources/views/email/dashboard.blade.php b/resources/views/email/dashboard.blade.php deleted file mode 100644 index 34d9b43..0000000 --- a/resources/views/email/dashboard.blade.php +++ /dev/null @@ -1,47 +0,0 @@ -@extends('layouts.email') -@section('title', 'Overview — Ladill Email') -@section('content') -
-
-
-

Overview

-

Your mailboxes at a glance.

-
- New mailbox -
- - @if($error)
{{ $error }}
@endif - -
- @foreach([['Mailboxes', $mailboxCount, route('email.mailboxes.index')], ['Domains', $domainCount, route('email.domains.index')], ['Verified domains', $verifiedDomainCount, route('email.domains.index')]] as [$label, $value, $link]) - -

{{ $label }}

-

{{ $value }}

-
- @endforeach -
- -
-
-

Recent mailboxes

- View all -
- @if(empty($recent)) -
-

No mailboxes yet

-

Add a domain, verify it, then create your first mailbox.

- Set up a domain -
- @else -
- @foreach($recent as $m) - - {{ $m['address'] }} - {{ $m['status'] }} - - @endforeach -
- @endif -
-
-@endsection diff --git a/resources/views/email/domains/index.blade.php b/resources/views/email/domains/index.blade.php deleted file mode 100644 index 7ff52db..0000000 --- a/resources/views/email/domains/index.blade.php +++ /dev/null @@ -1,30 +0,0 @@ -@extends('layouts.email') -@section('title', 'Domains — Ladill Email') -@section('content') -
-

Email domains

-

Add a domain and verify it to create mailboxes on it.

- @if($error)
{{ $error }}
@endif - -
- @csrf - - Add domain -
- - @if(!empty($domains)) - - @endif -
-@endsection diff --git a/resources/views/email/domains/show.blade.php b/resources/views/email/domains/show.blade.php deleted file mode 100644 index f1c75b5..0000000 --- a/resources/views/email/domains/show.blade.php +++ /dev/null @@ -1,71 +0,0 @@ -@extends('layouts.email') -@section('title', $domain['domain'].' — Ladill Email') -@section('content') -
-
- Domains/ - {{ $domain['domain'] }} -
-
-

{{ $domain['domain'] }}

- @if($domain['active'] ?? false) - Verified - @else - Pending - @endif -
- - @unless($domain['active'] ?? false) -
-

Publish these DNS records

- @php $records = $domain['dns_records'] ?? []; @endphp - @if(empty($records)) -

No DNS records returned. Try refreshing.

- @else - - - - - - @foreach($records as $r) - - - - - - @endforeach - -
TypeNameValue
{{ $r['type'] ?? '' }}{{ $r['name'] ?? ($r['host'] ?? '@') }}{{ $r['value'] ?? '' }}
- @endif -
-
- @csrf - - DNS changes can take time to propagate. -
- @else -
-

This domain is verified. Create a mailbox on it.

-
-
SPF {{ ($domain['spf'] ?? false) ? '✓' : '✗' }}
-
DKIM {{ ($domain['dkim'] ?? false) ? '✓' : '✗' }}
-
DMARC {{ ($domain['dmarc'] ?? false) ? '✓' : '✗' }}
-
-
- @endunless - - - - - - -
-@endsection diff --git a/resources/views/email/mailboxes/create.blade.php b/resources/views/email/mailboxes/create.blade.php deleted file mode 100644 index e53c52c..0000000 --- a/resources/views/email/mailboxes/create.blade.php +++ /dev/null @@ -1,97 +0,0 @@ -@extends('layouts.email') -@section('title', 'New mailbox — Ladill Email') -@section('content') -
-

Create a mailbox

- @if(empty($domains)) -
- You need a verified domain first. Set up a domain. -
- @else - @php $fmt = fn ($m) => $quota['currency'].' '.number_format($m / 100, 2); @endphp -
- {{-- Pricing banner (reacts to the selected storage plan) --}} - - - -
- @csrf -
- - -
-
- - - @error('local_part')

{{ $message }}

@enderror -
-
- - -
- - {{-- Storage plan (sets quota + price) --}} -
- -
- @foreach($quota['tiers'] as $t) - - @endforeach -
- @error('quota_mb')

{{ $message }}

@enderror -
- -
-
- - -
-
- - -
-
- @error('password')

{{ $message }}

@enderror - - -
-
- @endif -
-@endsection diff --git a/resources/views/email/mailboxes/index.blade.php b/resources/views/email/mailboxes/index.blade.php deleted file mode 100644 index f3a9f24..0000000 --- a/resources/views/email/mailboxes/index.blade.php +++ /dev/null @@ -1,32 +0,0 @@ -@extends('layouts.email') -@section('title', 'Mailboxes — Ladill Email') -@section('content') -
-
-

Mailboxes

- New mailbox -
- @if($error)
{{ $error }}
@endif - - @if(empty($mailboxes)) -
-

No mailboxes yet

-

Verify a domain, then create a mailbox like you@yourdomain.com.

- Create mailbox -
- @else - - @endif -
-@endsection diff --git a/resources/views/email/mailboxes/show.blade.php b/resources/views/email/mailboxes/show.blade.php deleted file mode 100644 index 256156a..0000000 --- a/resources/views/email/mailboxes/show.blade.php +++ /dev/null @@ -1,92 +0,0 @@ -@extends('layouts.email') -@section('title', $mailbox['address'].' — Ladill Email') -@section('content') -@php $host = 'mail.'.config('app.platform_domain'); @endphp -
-
- Mailboxes/ - {{ $mailbox['address'] }} -
-
-

{{ $mailbox['address'] }}

- {{ $mailbox['status'] ?? '' }} -
- -
-

Connection settings

-

Use these in any mail client, or just open webmail.

-
-
Username
{{ $mailbox['address'] }}
-
IMAP
{{ $host }}:993 (SSL)
-
SMTP
{{ $host }}:587 (STARTTLS)
-
- Open Webmail ↗ -
- - @php - $quotaMb = (int) ($mailbox['quota_mb'] ?? 0); - $usedBytes = (int) ($mailbox['used_bytes'] ?? 0); - $price = \App\Support\MailboxPricing::priceMinorFor($quotaMb); - $tiers = \App\Support\MailboxPricing::tiers(); - $maxMb = collect($tiers)->max('mb'); - $pct = $quotaMb > 0 ? min(100, (int) round($usedBytes / ($quotaMb * 1048576) * 100)) : 0; - $fmt = fn ($m) => config('email.currency', 'GHS').' '.number_format($m / 100, 2); - @endphp -
-
-
-

Storage plan

-

- {{ \App\Support\MailboxPricing::label($quotaMb) }} - @if($price === 0) - Free - @else - {{ $fmt($price) }}/month - @endif -

-
- @if($quotaMb < $maxMb) - Upgrade - @else - Top plan - @endif -
- @if($quotaMb > 0) -
-
-
-
-

{{ number_format($usedBytes / 1048576, 0) }} MB of {{ \App\Support\MailboxPricing::label($quotaMb) }} used ({{ $pct }}%)

-
- @endif -
- -
-

Reset password

-
- @csrf @method('PATCH') - - - -
-
- -
-

Delete mailbox

-

This permanently removes the mailbox and its email.

- - - - - -
-
-@endsection diff --git a/resources/views/email/mailboxes/upgrade.blade.php b/resources/views/email/mailboxes/upgrade.blade.php deleted file mode 100644 index ef6211d..0000000 --- a/resources/views/email/mailboxes/upgrade.blade.php +++ /dev/null @@ -1,52 +0,0 @@ -@extends('layouts.email') -@section('title', 'Upgrade storage — Ladill Email') -@section('content') -@php $fmt = fn ($m) => $currency.' '.number_format($m / 100, 2); @endphp -
-
- {{ $mailbox['address'] }}/ - Upgrade -
-

Upgrade storage

-

- Currently on {{ \App\Support\MailboxPricing::label($currentMb) }}. Pick a larger plan — billed monthly from your wallet. -

- -
-
- New plan: /month, charged now from your wallet - (balance: ). - Top up -
- -
- @csrf @method('PATCH') -
- -
- @foreach($tiers as $t) - - @endforeach -
- @error('quota_mb')

{{ $message }}

@enderror -
- -
-
-
-@endsection diff --git a/resources/views/email/signed-out.blade.php b/resources/views/email/signed-out.blade.php deleted file mode 100644 index 02f453e..0000000 --- a/resources/views/email/signed-out.blade.php +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Signed out — Ladill Email - @include('partials.favicon') - @vite(['resources/css/app.css']) - - - -
- Ladill Email -

You’ve been signed out. Redirecting…

- Go to ladill.com -
- - diff --git a/resources/views/layouts/hosting.blade.php b/resources/views/layouts/hosting.blade.php deleted file mode 100644 index 2d0f9ac..0000000 --- a/resources/views/layouts/hosting.blade.php +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - @yield('title', 'Ladill Hosting') - @include('partials.favicon') - - - @vite(['resources/css/app.css', 'resources/js/app.js']) - - -
-
- -
- @include('partials.topbar') -
- @include('partials.flash') - @yield('content') -
-
-
- @auth - @php - $navUser = auth()->user(); - $navInitials = collect(explode(' ', trim((string) $navUser?->name))) - ->filter()->take(2) - ->map(fn ($part) => strtoupper(substr($part, 0, 1))) - ->implode(''); - $navAcct = 'https://'.config('app.account_domain'); - @endphp - @include('partials.mobile-bottom-nav', [ - 'homeUrl' => route('hosting.dashboard'), - 'homeActive' => request()->routeIs('hosting.dashboard') || request()->routeIs('hosting.index'), - 'searchUrl' => route('hosting.dashboard'), - 'searchActive' => request()->routeIs('hosting.*'), - 'notificationsUrl' => route('notifications.index'), - 'notificationsActive' => request()->routeIs('notifications.*'), - 'unreadUrl' => route('notifications.unread'), - 'profileActive' => false, - 'profileName' => $navUser?->name ?? '', - 'profileSubtitle' => $navUser?->email ?? '', - 'profileMenuItems' => \App\Support\UserProfileMenu::items($navUser), - 'avatarUrl' => $navUser?->avatar_url, - 'initials' => $navInitials !== '' ? $navInitials : 'U', - ]) - @include('partials.afia') - @endauth -@include('partials.confirm-prompt') - - diff --git a/resources/views/layouts/user.blade.php b/resources/views/layouts/user.blade.php deleted file mode 100644 index 1ad0819..0000000 --- a/resources/views/layouts/user.blade.php +++ /dev/null @@ -1,260 +0,0 @@ -@php - $mobileFullScreenPage = request()->routeIs('account.settings') - || request()->routeIs('mini.payment-qrs.create') - || request()->routeIs('mini.payment-qrs.show'); - - $qrMobilePage = request()->routeIs('mini.payment-qrs.create') || request()->routeIs('mini.payment-qrs.show'); -@endphp - - $qrMobilePage])> - - - - - @include('partials.favicon') - {{ $title ?? 'Dashboard' }} - Ladill - - - @vite(['resources/css/app.css', 'resources/js/app.js']) - - -
- {{-- Mobile sidebar overlay --}} -
- - {{-- Sidebar — full product nav (Bird and other extracted apps have their - own nav in their own apps). --}} - @php $sidebarPartial = 'partials.sidebar'; @endphp - - - {{-- Main content --}} -
-
- @include('partials.topbar-qr') -
- -
$mobileFullScreenPage, - ])> - @include('partials.flash') -
- -
$qrMobilePage, - 'p-0 pb-24 lg:p-6 lg:pb-6' => $mobileFullScreenPage && ! $qrMobilePage, - 'p-6 pb-24 lg:pb-6' => ! $mobileFullScreenPage, - ])> - {{ $slot }} -
-
- - {{-- Mobile Bottom Navigation (hidden on QR create/show which have their own action bar) --}} - @unless(request()->routeIs('mini.payment-qrs.create') || request()->routeIs('mini.payment-qrs.show')) - @php - $navUser = auth()->user(); - $navInitials = collect(explode(' ', trim((string) $navUser?->name))) - ->filter()->take(2) - ->map(fn ($part) => strtoupper(substr($part, 0, 1))) - ->implode(''); - @endphp - @include('partials.mobile-bottom-nav', [ - 'homeUrl' => route('mini.dashboard'), - 'homeActive' => request()->routeIs('mini.dashboard'), - 'searchUrl' => route('mini.search'), - 'searchActive' => request()->routeIs('mini.search'), - 'notificationsUrl' => route('notifications.index'), - 'notificationsActive' => request()->routeIs('notifications.*'), - 'unreadUrl' => route('notifications.unread'), - 'profileActive' => request()->routeIs('account.settings'), - 'profileName' => $navUser?->name ?? '', - 'profileSubtitle' => $navUser?->email ?? '', - 'profileMenuItems' => \App\Support\UserProfileMenu::items($navUser), - 'avatarUrl' => $navUser?->avatarUrl(), - 'initials' => $navInitials !== '' ? $navInitials : 'U', - ]) - @endunless - - @if ($qrMobilePage) - - @endif -
- -@include('partials.afia') -@include('partials.sso-keepalive') -@include('partials.confirm-prompt') - - diff --git a/resources/views/mail/notifications/domain-verified.blade.php b/resources/views/mail/notifications/domain-verified.blade.php deleted file mode 100644 index 1db6f71..0000000 --- a/resources/views/mail/notifications/domain-verified.blade.php +++ /dev/null @@ -1,90 +0,0 @@ -@extends('mail.notifications.layout') - -@section('email-header') - @include('mail.partials.brand-header', ['brand' => 'domains']) -@endsection - -@section('email-footer') - @include('mail.partials.brand-footer', ['brand' => 'domains']) -@endsection - -@section('content') -
-

Domain Verified!

-

{{ $domain->host }} is now active

-
- -

Your Domain is Ready

- - - - - - - -
    -
  • Create email addresses like hello@{{ $domain->host }}
  • -
  • Connect this domain to your website
  • -
  • Manage DNS records from your dashboard
  • -
- -

- -
- -

-@endsection diff --git a/resources/views/mail/notifications/event-programme.blade.php b/resources/views/mail/notifications/event-programme.blade.php deleted file mode 100644 index ba5db16..0000000 --- a/resources/views/mail/notifications/event-programme.blade.php +++ /dev/null @@ -1,37 +0,0 @@ -@extends('mail.notifications.layout') - -@section('email-header') - @include('mail.partials.brand-header', ['brand' => 'events']) -@endsection - -@section('email-footer') - @include('mail.partials.brand-footer', ['brand' => 'events']) -@endsection - -@section('content') -
-

📋 Programme Outline

-

{{ $eventName }}

-
- -

Here's the programme{{ $attendeeName ? ', ' . explode(' ', $attendeeName)[0] : '' }}!

- - - - - - - -
- -
- - -@endsection diff --git a/resources/views/mail/notifications/hosting-activated.blade.php b/resources/views/mail/notifications/hosting-activated.blade.php deleted file mode 100644 index 4e38851..0000000 --- a/resources/views/mail/notifications/hosting-activated.blade.php +++ /dev/null @@ -1,64 +0,0 @@ -@extends('mail.notifications.layout') - -@section('email-header') - @include('mail.partials.brand-header', ['brand' => 'hosting']) -@endsection - -@section('email-footer') - @include('mail.partials.brand-footer', ['brand' => 'hosting']) -@endsection - -@section('content') -

Your hosting is now active! 🖥️

- - - -
-

{{ $planName }}

-

Your hosting is live and ready

-
- - - -

- -

- - - - -@endsection diff --git a/resources/views/mail/notifications/hosting-developer-added.blade.php b/resources/views/mail/notifications/hosting-developer-added.blade.php deleted file mode 100644 index 5a0048e..0000000 --- a/resources/views/mail/notifications/hosting-developer-added.blade.php +++ /dev/null @@ -1,47 +0,0 @@ -@extends('mail.notifications.layout') - -@section('email-header') - @include('mail.partials.brand-header', ['brand' => 'hosting']) -@endsection - -@section('email-footer') - @include('mail.partials.brand-footer', ['brand' => 'hosting']) -@endsection - -@section('content') -

You were added to a hosting team

-

- {{ $ownerName }} granted you developer access to the following Ladill hosting environment{{ count($accountLabels) === 1 ? '' : 's' }}: -

- -
    - @foreach ($accountLabels as $label) -
  • {{ $label }}
  • - @endforeach -
- -

- You now have full access to the hosting panel for {{ count($accountLabels) === 1 ? 'this account' : 'these accounts' }} — manage files, domains, databases, SSL, cron jobs, and more. -

- -

- To connect via SSH or SFTP, add your public key from the hosting panel Settings page after signing in. -

- - @if ($setupUrl) - -

- Use the button above to activate your account before signing in. -

- @else - - @endif - -

- After signing in, open your dashboard here: {{ $dashboardUrl }} -

-@endsection diff --git a/resources/views/mail/notifications/hosting-expiring.blade.php b/resources/views/mail/notifications/hosting-expiring.blade.php deleted file mode 100644 index dce0a10..0000000 --- a/resources/views/mail/notifications/hosting-expiring.blade.php +++ /dev/null @@ -1,56 +0,0 @@ -@extends('mail.notifications.layout') - -@section('email-header') - @include('mail.partials.brand-header', ['brand' => 'hosting']) -@endsection - -@section('email-footer') - @include('mail.partials.brand-footer', ['brand' => 'hosting']) -@endsection - -@section('content') -
-

Hosting Expiring Soon

-

{{ $account->primary_domain ?: $account->username }}

-
- -

Your Hosting Plan is Expiring Soon

- - - - - - - -

- -

- - - - -@endsection diff --git a/resources/views/mail/notifications/hosting-suspended.blade.php b/resources/views/mail/notifications/hosting-suspended.blade.php deleted file mode 100644 index f63ca41..0000000 --- a/resources/views/mail/notifications/hosting-suspended.blade.php +++ /dev/null @@ -1,59 +0,0 @@ -@extends('mail.notifications.layout') - -@section('email-header') - @include('mail.partials.brand-header', ['brand' => 'hosting']) -@endsection - -@section('email-footer') - @include('mail.partials.brand-footer', ['brand' => 'hosting']) -@endsection - -@section('content') -
-

Account Suspended

-

Action Required

-
- -

Your Hosting Account Has Been Suspended

- - - - - - - -

- -

- - - - -@endsection diff --git a/resources/views/mail/notifications/layout.blade.php b/resources/views/mail/notifications/layout.blade.php deleted file mode 100644 index 3662c00..0000000 --- a/resources/views/mail/notifications/layout.blade.php +++ /dev/null @@ -1,404 +0,0 @@ - - - - - - - {{ $subject ?? 'Ladill Notification' }} - - - - - - - diff --git a/resources/views/mail/notifications/ssl-expiring.blade.php b/resources/views/mail/notifications/ssl-expiring.blade.php deleted file mode 100644 index 99547a8..0000000 --- a/resources/views/mail/notifications/ssl-expiring.blade.php +++ /dev/null @@ -1,64 +0,0 @@ -@extends('mail.notifications.layout') - -@section('email-header') - @include('mail.partials.brand-header', ['brand' => 'hosting']) -@endsection - -@section('email-footer') - @include('mail.partials.brand-footer', ['brand' => 'hosting']) -@endsection - -@section('content') -
-

⚠️ SSL Certificate Expiring

-

{{ $daysUntilExpiry }} days remaining

-
- -

Action May Be Required

- - - - - - - -
    -
  • Ensure your domain's nameservers still point to Ladill
  • -
  • Verify your domain hasn't expired at your registrar
  • -
  • Check that your website is accessible
  • -
- -

- -

- - - - -@endsection diff --git a/resources/views/mail/notifications/ssl-provisioned.blade.php b/resources/views/mail/notifications/ssl-provisioned.blade.php deleted file mode 100644 index 8a3fa31..0000000 --- a/resources/views/mail/notifications/ssl-provisioned.blade.php +++ /dev/null @@ -1,64 +0,0 @@ -@extends('mail.notifications.layout') - -@section('email-header') - @include('mail.partials.brand-header', ['brand' => 'hosting']) -@endsection - -@section('email-footer') - @include('mail.partials.brand-footer', ['brand' => 'hosting']) -@endsection - -@section('content') -
-

🔒 SSL Certificate Active

-

{{ $domain->host }} is now secure

-
- -

Your Site is Secure

- - - - - - - -
    -
  • All traffic to your site is encrypted
  • -
  • Visitors see a secure padlock in their browser
  • -
  • Better search engine rankings (Google prefers HTTPS)
  • -
  • Certificate auto-renews before expiry
  • -
- -

- -

- - - - -@endsection diff --git a/resources/views/mail/partials/brand-footer.blade.php b/resources/views/mail/partials/brand-footer.blade.php deleted file mode 100644 index 6f6b66a..0000000 --- a/resources/views/mail/partials/brand-footer.blade.php +++ /dev/null @@ -1,20 +0,0 @@ -@php - $brandKey = $brand ?? 'ladill'; - $brandConfig = config("mail_brands.brands.{$brandKey}", config('mail_brands.brands.ladill')); - $appBase = rtrim((string) ($brandConfig['app_url'] ?? config('app.url')), '/'); - $dashboardPath = $brandConfig['dashboard_path'] ?? '/'; - $accountBase = rtrim((string) ($brandConfig['account_url'] ?? config('mail_brands.account_url', 'https://account.ladill.com')), '/'); - $supportUrl = $brandConfig['support_url'] ?? $accountBase.'/support-tickets'; - $homeLabel = $brandConfig['home_label'] ?? parse_url($appBase, PHP_URL_HOST) ?: 'ladill.com'; -@endphp - - - diff --git a/resources/views/mail/partials/brand-header.blade.php b/resources/views/mail/partials/brand-header.blade.php deleted file mode 100644 index f6bce5a..0000000 --- a/resources/views/mail/partials/brand-header.blade.php +++ /dev/null @@ -1,7 +0,0 @@ -@php - $brandKey = $brand ?? 'ladill'; - $brandConfig = config("mail_brands.brands.{$brandKey}", config('mail_brands.brands.ladill')); - $assetBase = rtrim((string) ($brandConfig['asset_url'] ?? config('app.url')), '/'); - $logoClass = trim((string) ($brandConfig['logo_class'] ?? 'email-logo')); -@endphp -{{ $brandConfig['name'] }} diff --git a/resources/views/notifications/index.blade.php b/resources/views/notifications/index.blade.php index 9901476..fd589e0 100644 --- a/resources/views/notifications/index.blade.php +++ b/resources/views/notifications/index.blade.php @@ -1,10 +1,6 @@ -@extends('layouts.hosting') - -@section('title', 'Notifications') - -@section('content') + @include('notifications._list', [ - 'subtitle' => 'Hosting activity for your account.', - 'emptyMessage' => "You're all caught up. We'll notify you when something happens with your hosting.", + 'subtitle' => 'Activity for your account.', + 'emptyMessage' => "You're all caught up. We'll notify you when something needs your attention.", ]) -@endsection + diff --git a/resources/views/partials/afia-button.blade.php b/resources/views/partials/afia-button.blade.php deleted file mode 100644 index 0588ba6..0000000 --- a/resources/views/partials/afia-button.blade.php +++ /dev/null @@ -1,24 +0,0 @@ -@props([ - 'compact' => false, - 'handler' => 'dispatch', -]) - -@auth - -@endauth diff --git a/resources/views/partials/afia.blade.php b/resources/views/partials/afia.blade.php deleted file mode 100644 index 1aff09d..0000000 --- a/resources/views/partials/afia.blade.php +++ /dev/null @@ -1,106 +0,0 @@ -@php - $afiaGreeting = "Hi, I'm Afia 👋 Ask me about payment QRs — creating one, accepting payments, payouts, or the 3.5% platform fee…"; - $afiaSuggestions = [ - 'How do I create a payment QR?', - 'How do customers pay me?', - 'When do payouts reach my wallet?', - 'What is the platform fee?', - ]; -@endphp -{{-- Afia — Ladill AI assistant slide-over. Opened via $dispatch('afia-open'). --}} -
-
- -
-
-
- - - - -
-

Afia

-

Mini assistant

-
-
-
- History - -
-
- -
-
- -
-
-
- - - -
-
-
-
- -
- -
-
- -
-
- - -
-

Afia can make mistakes — verify important details.

-
-
-
diff --git a/resources/views/partials/confirm-prompt.blade.php b/resources/views/partials/confirm-prompt.blade.php deleted file mode 100644 index c519339..0000000 --- a/resources/views/partials/confirm-prompt.blade.php +++ /dev/null @@ -1,65 +0,0 @@ -
-
- -
-
-
-
- -
-
-
- - -
-

-

-
- -
- - -
-
-
-
diff --git a/resources/views/partials/favicon.blade.php b/resources/views/partials/favicon.blade.php index 8933ddf..7474772 100644 --- a/resources/views/partials/favicon.blade.php +++ b/resources/views/partials/favicon.blade.php @@ -1,8 +1,6 @@ @php - $svgVer = @filemtime(public_path('favicon.svg')) ?: '1'; $icoVer = @filemtime(public_path('favicon.ico')) ?: '1'; @endphp - - - - + + + diff --git a/resources/views/partials/ladill-pro-icon.blade.php b/resources/views/partials/ladill-pro-icon.blade.php deleted file mode 100644 index 4ec9e3f..0000000 --- a/resources/views/partials/ladill-pro-icon.blade.php +++ /dev/null @@ -1,2 +0,0 @@ -@props(['class' => 'h-5 w-5']) - diff --git a/resources/views/partials/mailbox-link-banner.blade.php b/resources/views/partials/mailbox-link-banner.blade.php deleted file mode 100644 index 8e01144..0000000 --- a/resources/views/partials/mailbox-link-banner.blade.php +++ /dev/null @@ -1,46 +0,0 @@ -@if(($mailboxLinkReminder['visible'] ?? false) === true) - @php - $stage = $mailboxLinkReminder['stage'] ?? 'needs_link'; - $copy = match ($stage) { - 'needs_domain' => [ - 'title' => 'Set up Ladill Email', - 'body' => 'Add and verify an email domain before you can create a mailbox and link it to your Ladill account.', - 'cta' => 'Add email domain', - 'url' => route('email.domains.index'), - ], - 'needs_mailbox' => [ - 'title' => 'Create your Ladill mailbox', - 'body' => 'Your domain is ready. Create a mailbox, then link it in Settings so Ladill Mail opens with your Ladill sign-in.', - 'cta' => 'Create mailbox', - 'url' => route('email.mailboxes.create'), - ], - default => [ - 'title' => 'Link your Ladill mailbox', - 'body' => 'Your account uses '.$mailboxLinkReminder['account_email'].' — choose a mailbox in Settings to connect Ladill Mail sign-in.', - 'cta' => 'Link mailbox', - 'url' => route('account.settings'), - ], - }; - @endphp -
-
- -
-

{{ $copy['title'] }}

-

{{ $copy['body'] }}

- - {{ $copy['cta'] }} - - -
-
- @csrf - -
-
-
-@endif diff --git a/resources/views/partials/mini-paystack-frame-footer.blade.php b/resources/views/partials/mini-paystack-frame-footer.blade.php deleted file mode 100644 index b08ecc4..0000000 --- a/resources/views/partials/mini-paystack-frame-footer.blade.php +++ /dev/null @@ -1,3 +0,0 @@ -
-

Secured by Paystack. Powered by Ladill Pay

-
diff --git a/resources/views/partials/mini-paystack-frame-header.blade.php b/resources/views/partials/mini-paystack-frame-header.blade.php deleted file mode 100644 index bdee833..0000000 --- a/resources/views/partials/mini-paystack-frame-header.blade.php +++ /dev/null @@ -1,15 +0,0 @@ -
-
-
- Ladill Mini - -
-
diff --git a/resources/views/partials/mobile-bottom-nav.blade.php b/resources/views/partials/mobile-bottom-nav.blade.php deleted file mode 100644 index 8613313..0000000 --- a/resources/views/partials/mobile-bottom-nav.blade.php +++ /dev/null @@ -1,123 +0,0 @@ -@php - $gridCols = !empty($centerCompose) ? 'grid-cols-5' : 'grid-cols-4'; - $avatarUrl = $avatarUrl ?? null; - $initials = $initials ?? 'U'; - $notificationsUrl = $notificationsUrl ?? '#'; - $unreadUrl = $unreadUrl ?? null; - $profileUrl = $profileUrl ?? '#'; - $profileName = trim((string) ($profileName ?? '')); - $profileSubtitle = trim((string) ($profileSubtitle ?? '')); - $profileMenuItems = $profileMenuItems ?? []; - if ($profileMenuItems === [] && $profileUrl !== '#') { - $profileMenuItems = [['type' => 'link', 'label' => 'Profile', 'href' => $profileUrl]]; - } - $navActive = fn (bool $active) => $active ? 'text-indigo-600' : 'text-slate-600'; -@endphp -
- - - {{-- Profile menu bottom sheet (matches desktop avatar dropdown) --}} -
-
- -
-
- -
- - @if ($profileName !== '' || $profileSubtitle !== '') -
- @if ($avatarUrl) - - @else - {{ $initials }} - @endif -
- @if ($profileName !== '') -

{{ $profileName }}

- @endif - @if ($profileSubtitle !== '') -

{{ $profileSubtitle }}

- @endif -
-
- @endif - - @include('partials.user-profile-menu', [ - 'items' => $profileMenuItems, - 'variant' => 'sheet', - 'onNavigate' => 'profileOpen = false', - ]) -
-
-
diff --git a/resources/views/partials/mobile-topbar-title.blade.php b/resources/views/partials/mobile-topbar-title.blade.php deleted file mode 100644 index ff10b4a..0000000 --- a/resources/views/partials/mobile-topbar-title.blade.php +++ /dev/null @@ -1,6 +0,0 @@ -@php - $title = $mobileTopbarTitle ?? 'Ladill'; -@endphp -
-

{{ $title }}

-
diff --git a/resources/views/partials/paystack-sheet.blade.php b/resources/views/partials/paystack-sheet.blade.php deleted file mode 100644 index ce2f127..0000000 --- a/resources/views/partials/paystack-sheet.blade.php +++ /dev/null @@ -1,44 +0,0 @@ -{{-- - Paystack mobile bottom-sheet iframe. - Requires Alpine.js ancestor with: showSheet (bool), checkoutUrl (string). - On mobile (< md) the sheet slides up; on desktop nothing renders. ---}} - diff --git a/resources/views/partials/search-screen.blade.php b/resources/views/partials/search-screen.blade.php deleted file mode 100644 index be97ed7..0000000 --- a/resources/views/partials/search-screen.blade.php +++ /dev/null @@ -1,100 +0,0 @@ -@php - $searchUrl = $searchUrl ?? url('/search'); -@endphp - -
- -
-
- - -
-
-
- - - -
- - - - - - - -
-
diff --git a/resources/views/partials/sidebar-support.blade.php b/resources/views/partials/sidebar-support.blade.php deleted file mode 100644 index 817932f..0000000 --- a/resources/views/partials/sidebar-support.blade.php +++ /dev/null @@ -1,23 +0,0 @@ -@php - $useInternal = $internal ?? false; - if ($useInternal) { - $supportUrl = route('user.support-tickets.index'); - $openExternal = false; - } else { - $supportUrl = function_exists('ladill_account_url') - ? ladill_account_url('/support-tickets') - : 'https://'.config('app.account_domain', 'account.ladill.com').'/support-tickets'; - $openExternal = true; - } -@endphp - - - Support - @if($openExternal) - - @endif - diff --git a/resources/views/partials/sso-keepalive.blade.php b/resources/views/partials/sso-keepalive.blade.php deleted file mode 100644 index 2d297c5..0000000 --- a/resources/views/partials/sso-keepalive.blade.php +++ /dev/null @@ -1,21 +0,0 @@ -@if (auth()->check()) - @php - $authPing = 'https://'.config('app.auth_domain').'/sso/ping'; - $platformSignedOutUrl = route('sso.platform-signed-out', ['redirect' => url()->current()]); - @endphp - -@endif diff --git a/resources/views/partials/topbar-account-switcher.blade.php b/resources/views/partials/topbar-account-switcher.blade.php deleted file mode 100644 index dfcaf02..0000000 --- a/resources/views/partials/topbar-account-switcher.blade.php +++ /dev/null @@ -1,24 +0,0 @@ -{{-- Account switcher (desktop) — only when the user belongs to more than one account. --}} -@if (isset($accessibleAccounts) && $accessibleAccounts->count() > 1) - -@endif diff --git a/resources/views/partials/topbar-desktop-widgets.blade.php b/resources/views/partials/topbar-desktop-widgets.blade.php index bb1eca5..9bd2e1d 100644 --- a/resources/views/partials/topbar-desktop-widgets.blade.php +++ b/resources/views/partials/topbar-desktop-widgets.blade.php @@ -1,6 +1,6 @@ {{-- - Standard desktop top-right widgets (CRM / Invoice layout): - Afia → notifications → launcher → divider → avatar dropdown. + Standard desktop top-right widgets: + notifications → launcher → divider → avatar dropdown. --}} @php $topbarUser = $user ?? auth()->user(); @@ -12,8 +12,6 @@ $showUserHeader = $showUser ?? true; @endphp -@includeIf('partials.afia-button', ['compact' => true]) - @includeIf('partials.notification-dropdown') @include('partials.launcher') diff --git a/resources/views/partials/topbar-qr.blade.php b/resources/views/partials/topbar-qr.blade.php deleted file mode 100644 index 24232d0..0000000 --- a/resources/views/partials/topbar-qr.blade.php +++ /dev/null @@ -1,29 +0,0 @@ -@php - $user = auth()->user(); - $initials = collect(explode(' ', trim((string) $user?->name))) - ->filter() - ->take(2) - ->map(fn ($part) => strtoupper(substr($part, 0, 1))) - ->implode(''); -@endphp - -
-
- - @include('partials.mobile-topbar-title') -
- -
- @auth - @includeIf('partials.topbar-account-switcher') - @includeIf('partials.topbar-widgets-prepend') - @include('partials.topbar-desktop-widgets', ['user' => $user ?? $u ?? auth()->user(), 'showUser' => true]) - @includeIf('partials.topbar-widgets-append') - @else - Sign in - @include('partials.launcher') - @endauth -
-
diff --git a/resources/views/partials/wordpress-icon.blade.php b/resources/views/partials/wordpress-icon.blade.php deleted file mode 100644 index a92f0e7..0000000 --- a/resources/views/partials/wordpress-icon.blade.php +++ /dev/null @@ -1,2 +0,0 @@ -@props(['class' => 'h-8 w-8']) -WordPress diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php deleted file mode 100644 index d92ac19..0000000 --- a/resources/views/welcome.blade.php +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - - {{ config('app.name', 'Laravel') }} - @include('partials.favicon') - - - - - - - @if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot'))) - @vite(['resources/css/app.css', 'resources/js/app.js']) - @else - - @endif - - -
- @if (Route::has('login')) - - @endif -
-
-
-
-

Let's get started

-

Laravel has an incredibly rich ecosystem.
We suggest starting with the following.

- - -
-
- {{-- Laravel Logo --}} - - - - - - - - - - - {{-- Light Mode 12 SVG --}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{-- Dark Mode 12 SVG --}} - -
-
-
-
- - @if (Route::has('login')) - - @endif - - diff --git a/tests/Feature/PosCommerceTest.php b/tests/Feature/PosCommerceTest.php index f846a4c..27e9607 100644 --- a/tests/Feature/PosCommerceTest.php +++ b/tests/Feature/PosCommerceTest.php @@ -64,10 +64,19 @@ class PosCommerceTest extends TestCase [ 'name' => 'Imported Mug', 'sku' => 'MUG-1', + 'type' => 'product', 'unit_price_minor' => 2500, 'currency' => 'GHS', 'active' => true, ], + [ + 'name' => 'Consulting Hour', + 'sku' => 'SVC-1', + 'type' => 'service', + 'unit_price_minor' => 9000, + 'currency' => 'GHS', + 'active' => true, + ], ], ], 200), ]); @@ -83,6 +92,13 @@ class PosCommerceTest extends TestCase $this->assertNotNull($product); $this->assertSame('Imported Mug', $product->name); $this->assertSame(2500, $product->price_minor); + + // Services must never be imported as POS products. + $this->assertNull(PosProduct::owned($user->public_id)->where('sku', 'SVC-1')->first()); + + // The CRM request must constrain to products only. + Http::assertSent(fn ($request) => str_contains($request->url(), 'crm.test/api/products') + && str_contains($request->url(), 'type=product')); } public function test_paid_sale_links_to_invoice_prefill(): void diff --git a/tests/Feature/PosRegisterTest.php b/tests/Feature/PosRegisterTest.php index 263eaa2..926135b 100644 --- a/tests/Feature/PosRegisterTest.php +++ b/tests/Feature/PosRegisterTest.php @@ -46,7 +46,7 @@ class PosRegisterTest extends TestCase ->get(route('pos.dashboard')) ->assertOk() ->assertSee('Overview') - ->assertSee('favicon.svg', false); + ->assertSee('favicon.ico', false); } public function test_register_renders_products(): void