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 c2b46bd..2a03e3f 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/favicon.svg b/public/favicon.svg deleted file mode 100644 index 84b9280..0000000 --- a/public/favicon.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ 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