├── .circleci └── config.yml ├── .docker ├── config │ ├── conf.d │ │ └── default.conf │ ├── config │ │ ├── conf.d │ │ │ └── default.conf │ │ ├── fpm-pool.conf │ │ ├── nginx.conf │ │ ├── php.ini │ │ └── supervisord.conf │ ├── fpm-pool.conf │ ├── nginx.conf │ ├── php.ini │ └── supervisord.conf ├── octane │ ├── .rr.prod.yaml │ ├── FrankenPHP │ │ └── supervisord.frankenphp.conf │ ├── RoadRunner │ │ ├── .rr.prod.yaml │ │ └── supervisord.roadrunner.conf │ ├── Swoole │ │ └── supervisord.swoole.conf │ ├── entrypoint.sh │ ├── opcache.ini │ ├── php.ini │ ├── supervisord.app.conf │ ├── supervisord.app.roadrunner.conf │ ├── supervisord.horizon.conf │ └── utilities.sh ├── php.ini ├── start-container ├── supervisord.conf ├── supervisord.horizon.conf ├── supervisord.scheduler.conf ├── supervisord.worker.conf └── utilities.sh ├── .env.example ├── .env.testing ├── .github ├── FUNDING.yml ├── dependabot.yml ├── issue_template.md └── workflows │ ├── install.yml │ ├── main.yml │ ├── security.yml │ └── tests.yml ├── .gitignore ├── Dockerfile ├── README.md ├── app ├── Actions │ ├── Fortify │ │ ├── CreateNewUser.php │ │ ├── CreateNewUserWithTeams.php │ │ ├── PasswordValidationRules.php │ │ ├── ResetUserPassword.php │ │ ├── UpdateUserPassword.php │ │ └── UpdateUserProfileInformation.php │ ├── Jetstream │ │ ├── AddTeamMember.php │ │ ├── CreateTeam.php │ │ ├── DeleteTeam.php │ │ ├── DeleteUser.php │ │ ├── DeleteUserWithTeams.php │ │ ├── InviteTeamMember.php │ │ ├── RemoveTeamMember.php │ │ └── UpdateTeamName.php │ └── Socialstream │ │ ├── CreateConnectedAccount.php │ │ ├── CreateUserFromProvider.php │ │ ├── CreateUserWithTeamsFromProvider.php │ │ ├── GenerateRedirectForProvider.php │ │ ├── HandleInvalidState.php │ │ ├── ResolveSocialiteUser.php │ │ ├── SetUserPassword.php │ │ └── UpdateConnectedAccount.php ├── Console │ ├── Commands │ │ ├── ProcessInvoiceReminders.php │ │ ├── PruneAuditLogs.php │ │ ├── SendInvoiceReminders.php │ │ └── SyncEnomDomains.php │ └── Kernel.php ├── Events │ └── InvoiceStatusChanged.php ├── Exceptions │ └── Handler.php ├── Filament │ ├── Admin │ │ └── Resources │ │ │ ├── MenuResource │ │ │ └── Pages │ │ │ │ ├── CreateMenu.php │ │ │ │ ├── EditMenu.php │ │ │ │ └── ListMenus.php │ │ │ └── SiteSettingsResource │ │ │ └── Pages │ │ │ ├── CreateSiteSettings.php │ │ │ ├── EditSiteSettings.php │ │ │ └── ListSiteSettings.php │ ├── App │ │ ├── Pages │ │ │ ├── .gitignore │ │ │ ├── CreateTeam.php │ │ │ ├── EditProfile.php │ │ │ ├── EditTeam.php │ │ │ ├── PersonalAccessTokensPage.php │ │ │ └── UpdateProfileInformationPage.php │ │ ├── Resources │ │ │ ├── .gitignore │ │ │ ├── AffiliateResource.php │ │ │ ├── AffiliateResource │ │ │ │ └── Pages │ │ │ │ │ ├── CreateAffiliate.php │ │ │ │ │ ├── EditAffiliate.php │ │ │ │ │ └── ListAffiliates.php │ │ │ ├── HostingAccountResource.php │ │ │ ├── HostingAccountResource │ │ │ │ └── Pages │ │ │ │ │ ├── CreateHostingAccount.php │ │ │ │ │ ├── EditHostingAccount.php │ │ │ │ │ └── ListHostingAccounts.php │ │ │ ├── PartialPaymentResource.php │ │ │ ├── PartialPaymentResource │ │ │ │ └── Pages │ │ │ │ │ ├── CreatePartialPayment.php │ │ │ │ │ ├── EditPartialPayment.php │ │ │ │ │ └── ListPartialPayments.php │ │ │ ├── ProductsServiceResource.php │ │ │ ├── ProductsServiceResource │ │ │ │ └── Pages │ │ │ │ │ ├── CreateProductsService.php │ │ │ │ │ ├── EditProductsService.php │ │ │ │ │ └── ListProductsServices.php │ │ │ ├── RefundResource.php │ │ │ ├── RefundResource │ │ │ │ └── Pages │ │ │ │ │ ├── CreateRefund.php │ │ │ │ │ ├── EditRefund.php │ │ │ │ │ └── ListRefunds.php │ │ │ ├── UserResource.php │ │ │ └── UserResource │ │ │ │ └── Pages │ │ │ │ ├── CreateUser.php │ │ │ │ ├── EditUser.php │ │ │ │ └── ListUsers.php │ │ └── Widgets │ │ │ └── .gitignore │ ├── Client │ │ ├── Pages │ │ │ ├── Dashboard.php │ │ │ └── Profile.php │ │ └── Resources │ │ │ └── InvoiceResource.php │ ├── Components │ │ └── PaymentForm.php │ ├── Pages │ │ ├── ManageSubscriptionPage.php │ │ └── SubscriptionPlansPage.php │ └── Resources │ │ ├── AuditLogResource.php │ │ ├── HostingServerResource.php │ │ ├── PaymentGatewayResource.php │ │ ├── PaymentPlanResource.php │ │ ├── RecurringBillingResource.php │ │ ├── RoleResource.php │ │ └── SubscriptionResource.php ├── Http │ ├── Controllers │ │ ├── Api │ │ │ ├── AuthController.php │ │ │ └── InvoiceController.php │ │ ├── Client │ │ │ └── ServiceManagementController.php │ │ ├── ClientController.php │ │ ├── ClientNoteController.php │ │ ├── Controller.php │ │ ├── DashboardController.php │ │ ├── DiscountController.php │ │ ├── EmailTemplateController.php │ │ ├── FileController.php │ │ ├── ForgotPasswordController.php │ │ ├── InstallationController.php │ │ ├── IntegrationController.php │ │ ├── InvoiceDisputeController.php │ │ ├── InvoiceTemplateController.php │ │ ├── LateFeeConfigurationController.php │ │ ├── OrderController.php │ │ ├── PaymentController.php │ │ ├── PaymentHistoryController.php │ │ ├── PaymentReconciliationController.php │ │ ├── ReminderSettingsController.php │ │ ├── ReportController.php │ │ ├── ResetPasswordController.php │ │ ├── SavedSearchController.php │ │ ├── ServiceManagementController.php │ │ ├── TeamInvitationController.php │ │ └── TicketController.php │ ├── Kernel.php │ ├── Livewire │ │ ├── CreateTeam.php │ │ └── Dashboard.php │ ├── Middleware │ │ ├── AssignDefaultTeam.php │ │ ├── Authenticate.php │ │ ├── CheckRole.php │ │ ├── EncryptCookies.php │ │ ├── PreventRequestsDuringMaintenance.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── RequireTwoFactorEnabled.php │ │ ├── RoleBasedRedirect.php │ │ ├── ScreeningDataEncryptor.php │ │ ├── TeamsPermission.php │ │ ├── TrimStrings.php │ │ ├── TrustHosts.php │ │ ├── TrustProxies.php │ │ ├── ValidateSignature.php │ │ └── VerifyCsrfToken.php │ └── Resources │ │ └── Api │ │ └── InvoiceResource.php ├── Jobs │ ├── ProcessSubscriptionBilling.php │ └── SendEmailNotification.php ├── Listeners │ ├── CreatePersonalTeam.php │ ├── EmailTracker.php │ └── SwitchTeam.php ├── Mail │ ├── InvoiceGenerated.php │ ├── OverdueInvoiceReminder.php │ ├── PaymentConfirmation.php │ └── UpcomingInvoiceReminder.php ├── Models │ ├── Affiliate.php │ ├── AuditLog.php │ ├── Client.php │ ├── ClientNote.php │ ├── ConnectedAccount.php │ ├── Credit.php │ ├── Currency.php │ ├── Customer.php │ ├── Discount.php │ ├── EmailTemplate.php │ ├── File.php │ ├── Folder.php │ ├── HostingAccount.php │ ├── Integration.php │ ├── Invoice.php │ ├── InvoiceDispute.php │ ├── InvoiceTemplate.php │ ├── Invoice_Item.php │ ├── LateFeeConfiguration.php │ ├── Membership.php │ ├── Menu.php │ ├── Payment.php │ ├── PaymentGateway.php │ ├── PaymentHistory.php │ ├── PaymentPlan.php │ ├── Products_Service.php │ ├── RecurringBillingConfiguration.php │ ├── ReminderSetting.php │ ├── Report.php │ ├── Role.php │ ├── SavedSearch.php │ ├── Subscription.php │ ├── SubscriptionPlan.php │ ├── TaxExemption.php │ ├── TaxRate.php │ ├── Team.php │ ├── TeamInvitation.php │ ├── Ticket.php │ ├── TicketResponse.php │ ├── Tld.php │ ├── UsageRecord.php │ └── User.php ├── Notifications │ └── NewTicketNotification.php ├── Policies │ ├── AffiliatePolicy.php │ ├── ConnectedAccountPolicy.php │ ├── FilePolicy.php │ ├── HostingAccountPolicy.php │ ├── InvoicePolicy.php │ ├── InvoiceTemplatePolicy.php │ ├── PaymentPolicy.php │ ├── Products_ServicePolicy.php │ ├── RolePolicy.php │ ├── TeamPolicy.php │ ├── TicketPolicy.php │ └── UserPolicy.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ ├── Filament │ │ ├── AdminPanelProvider.php │ │ ├── AppPanelProvider.php │ │ └── ClientPanelProvider.php │ ├── FortifyServiceProvider.php │ ├── JetstreamServiceProvider.php │ ├── RouteServiceProvider.php │ ├── SocialstreamServiceProvider.php │ └── TeamServiceProvider.php ├── Services │ ├── AffiliateReportingService.php │ ├── AffiliateService.php │ ├── AuditLogService.php │ ├── BillingService.php │ ├── ClientBillingReportService.php │ ├── ControlPanels │ │ ├── CpanelClient.php │ │ ├── DirectAdminClient.php │ │ ├── PleskClient.php │ │ └── SoftaculousClient.php │ ├── CurrencyService.php │ ├── DisputeService.php │ ├── DomainPricingService.php │ ├── DomainService.php │ ├── EmailNotificationService.php │ ├── HostingService.php │ ├── InstallationScriptService.php │ ├── IntegrationService.php │ ├── MenuService.php │ ├── PartialPaymentService.php │ ├── PaymentGatewayService.php │ ├── PaymentPlanService.php │ ├── PaymentReconciliationService.php │ ├── PricingService.php │ ├── RefundService.php │ ├── Registrars │ │ ├── EnomClient.php │ │ └── ResellerClubClient.php │ ├── ReportExportService.php │ ├── ReportGenerationService.php │ ├── ReportService.php │ ├── ServiceProvisioningService.php │ ├── SiteSettingsService.php │ ├── SmsService.php │ └── TaxService.php ├── Traits │ └── HasTeam.php └── View │ └── Components │ ├── AppLayout.php │ └── GuestLayout.php ├── artisan ├── bootstrap ├── app.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── cors.php ├── database.php ├── filament-shield.php ├── filesystems.php ├── fortify.php ├── hashing.php ├── jetstream.php ├── logging.php ├── mail.php ├── octane.php ├── permission.php ├── queue.php ├── sanctum.php ├── services.php ├── session.php ├── socialstream.php └── view.php ├── database ├── .gitignore ├── factories │ ├── ConnectedAccountFactory.php │ ├── CustomerFactory.php │ ├── InvoiceFactory.php │ ├── InvoiceItemFactory.php │ ├── MenuFactory.php │ ├── PaymentFactory.php │ ├── ProductsServiceFactory.php │ ├── SubscriptionFactory.php │ ├── TeamFactory.php │ └── UserFactory.php ├── migrations │ ├── 0001_01_01_000000_create_users_table.php │ ├── 0001_01_01_000002_create_connected_accounts_table.php │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ ├── 2020_05_21_100000_create_teams_table.php │ ├── 2020_05_21_200000_create_team_user_table.php │ ├── 2020_05_21_300000_create_team_invitations_table.php │ ├── 2023_05_25_000000_create_affiliates_table.php │ ├── 2024_01_01_create_file_shares_table.php │ ├── 2024_01_01_create_files_table.php │ ├── 2024_01_01_create_folders_table.php │ ├── 2024_01_10_000000_create_audit_logs_table.php │ ├── 2024_01_10_000000_create_client_notes_table.php │ ├── 2024_01_10_000000_create_discounts_table.php │ ├── 2024_01_10_000000_create_integrations_table.php │ ├── 2024_01_10_000000_create_tax_rates_table.php │ ├── 2024_01_10_000001_create_late_fee_configurations_table.php │ ├── 2024_01_10_000001_create_recurring_billing_configurations_table.php │ ├── 2024_01_10_000001_create_tickets_table.php │ ├── 2024_01_10_000002_create_ticket_responses_table.php │ ├── 2024_01_20_000000_add_dashboard_preferences_to_users_table.php │ ├── 2024_01_20_000000_create_audit_logs_table.php │ ├── 2024_01_20_000000_create_clients_table.php │ ├── 2024_01_20_000000_create_reports_table.php │ ├── 2024_01_20_000001_create_email_templates_table.php │ ├── 2024_01_20_000001_create_invoice_disputes_table.php │ ├── 2024_01_20_000001_create_product_types_table.php │ ├── 2024_01_20_000001_create_tax_exemptions_table.php │ ├── 2024_01_20_000002_create_hosting_servers_table.php │ ├── 2024_01_20_create_saved_searches_table.php │ ├── 2024_01_20_create_subscription_plans_table.php │ ├── 2024_01_21_000001_create_reminder_settings_table.php │ ├── 2024_01_25_000000_create_usage_records_table.php │ ├── 2024_01_25_000001_create_payment_plans_table.php │ ├── 2024_02_15_000000_create_invoice_templates_table.php │ ├── 2024_02_15_000001_create_payment_histories_table.php │ ├── 2024_02_21_190705_create_permission_tables.php │ ├── 2024_06_17_143940_create_customers_table.php │ ├── 2024_06_17_145015_create_products_services_table.php │ ├── 2024_06_17_145508_create_invoices_table.php │ ├── 2024_06_17_150814_create_invoice_items_table.php │ ├── 2024_06_17_151700_create_payments_table.php │ ├── 2024_06_17_152358_create_subscriptions_table.php │ ├── 2024_06_18_000000_create_credits_table.php │ ├── 2024_06_18_000000_create_currencies_table.php │ ├── 2024_06_18_000000_create_hosting_accounts_table.php │ ├── 2024_06_18_000001_add_refund_status_to_payments_table.php │ ├── 2024_07_24_080000_create_menus_table.php │ ├── 2024_07_29_create_payment_gateways_table.php │ └── 2024_08_29_093707_add_team_to_resources.php └── seeders │ ├── DatabaseSeeder.php │ ├── DefaultTeamSeeder.php │ ├── EmailTemplateSeeder.php │ ├── MenuSeeder.php │ ├── PermissionsSeeder.php │ ├── PermissionsTableSeeder.php │ ├── RolesSeeder.php │ ├── SiteSettingsSeeder.php │ ├── TeamSeeder.php │ └── UserSeeder.php ├── docs └── api.yaml ├── error_log ├── package-lock.json ├── package.json ├── phpunit.xml ├── postcss.config.cjs ├── postcss.config.js ├── public ├── .htaccess ├── build │ ├── images │ │ └── logo.png │ └── manifest.json ├── css │ ├── filament │ │ ├── filament │ │ │ └── app.css │ │ ├── forms │ │ │ └── forms.css │ │ └── support │ │ │ └── support.css │ └── reports.css ├── favicon.ico ├── index.php ├── js │ └── filament │ │ ├── filament │ │ ├── app.js │ │ └── echo.js │ │ ├── forms │ │ └── components │ │ │ ├── color-picker.js │ │ │ ├── date-time-picker.js │ │ │ ├── file-upload.js │ │ │ ├── key-value.js │ │ │ ├── markdown-editor.js │ │ │ ├── rich-editor.js │ │ │ ├── select.js │ │ │ ├── tags-input.js │ │ │ └── textarea.js │ │ ├── notifications │ │ └── notifications.js │ │ ├── support │ │ ├── async-alpine.js │ │ └── support.js │ │ ├── tables │ │ └── components │ │ │ └── table.js │ │ └── widgets │ │ └── components │ │ ├── chart.js │ │ └── stats-overview │ │ └── stat │ │ └── chart.js └── robots.txt ├── resources ├── css │ ├── app.css │ └── filament │ │ └── admin │ │ ├── tailwind.config.js │ │ └── theme.css ├── images │ └── logo.png ├── js │ ├── Components │ │ └── InvoiceStatusTracker.vue │ ├── app.js │ └── components │ │ ├── AdvancedSearch.vue │ │ ├── ClientNotes.vue │ │ └── Dashboard.vue ├── markdown │ ├── policy.md │ └── terms.md └── views │ ├── api │ ├── api-token-manager.blade.php │ └── index.blade.php │ ├── auth │ ├── confirm-password.blade.php │ ├── forgot-password.blade.php │ ├── login.blade.php │ ├── register.blade.php │ ├── reset-password.blade.php │ ├── two-factor-challenge.blade.php │ └── verify-email.blade.php │ ├── client │ └── services │ │ ├── index.blade.php │ │ └── show.blade.php │ ├── clients │ └── index.blade.php │ ├── components │ ├── action-link.blade.php │ ├── action-message.blade.php │ ├── action-section.blade.php │ ├── application-logo.blade.php │ ├── application-mark.blade.php │ ├── authentication-card-logo.blade.php │ ├── authentication-card.blade.php │ ├── banner.blade.php │ ├── button.blade.php │ ├── buttons.blade.php │ ├── checkbox.blade.php │ ├── confirmation-modal.blade.php │ ├── confirms-password.blade.php │ ├── connected-account.blade.php │ ├── contact-form.blade.php │ ├── danger-button.blade.php │ ├── dialog-modal.blade.php │ ├── dropdown-link.blade.php │ ├── dropdown.blade.php │ ├── footer.blade.php │ ├── form-section.blade.php │ ├── header.blade.php │ ├── home-header.blade.php │ ├── home-navbar.blade.php │ ├── input-error.blade.php │ ├── input.blade.php │ ├── label.blade.php │ ├── layouts │ │ └── app.blade.php │ ├── logo.blade.php │ ├── manage_section.blade.php │ ├── modal.blade.php │ ├── nav-link.blade.php │ ├── products_section.blade.php │ ├── responsive-nav-link.blade.php │ ├── secondary-button.blade.php │ ├── section-border.blade.php │ ├── section-title.blade.php │ ├── socialstream-icons │ │ ├── bitbucket.blade.php │ │ ├── facebook.blade.php │ │ ├── github.blade.php │ │ ├── gitlab.blade.php │ │ ├── google.blade.php │ │ ├── linkedin.blade.php │ │ ├── provider-icon.blade.php │ │ ├── slack.blade.php │ │ └── twitter.blade.php │ ├── socialstream.blade.php │ ├── switchable-team.blade.php │ ├── validation-errors.blade.php │ ├── welcome.blade.php │ └── why_us_section.blade.php │ ├── emails │ ├── dynamic-template.blade.php │ └── team-invitation.blade.php │ ├── filament │ ├── filament │ │ └── pages │ │ │ ├── api-tokens.blade.php │ │ │ ├── create-team.blade.php │ │ │ ├── edit-profile.blade.php │ │ │ └── edit-team.blade.php │ └── pages │ │ ├── api-tokens.blade.php │ │ ├── create-team.blade.php │ │ ├── edit-profile.blade.php │ │ └── edit-team.blade.php │ ├── invoice-templates │ ├── form.blade.php │ └── index.blade.php │ ├── layouts │ ├── app.blade.php │ └── guest.blade.php │ ├── livewire │ └── dashboard.blade.php │ ├── orders │ └── create.blade.php │ ├── payment-history │ └── index.blade.php │ ├── payment-reconciliation │ └── index.blade.php │ ├── pdfs │ └── invoice.blade.php │ ├── policy.blade.php │ ├── profile │ ├── connected-accounts-form.blade.php │ ├── delete-user-form.blade.php │ ├── logout-other-browser-sessions-form.blade.php │ ├── set-password-form.blade.php │ ├── show.blade.php │ ├── two-factor-authentication-form.blade.php │ ├── update-password-form.blade.php │ └── update-profile-information-form.blade.php │ ├── reminder-settings │ └── edit.blade.php │ ├── reports │ ├── create.blade.php │ └── templates │ │ └── client_billing.blade.php │ ├── teams │ ├── create-team-form.blade.php │ ├── create.blade.php │ ├── delete-team-form.blade.php │ ├── show.blade.php │ ├── team-member-manager.blade.php │ └── update-team-name-form.blade.php │ ├── terms.blade.php │ └── welcome.blade.php ├── routes ├── api.php ├── channels.php ├── console.php ├── socialstream.php └── web.php ├── setup.sh ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tailwind.config.js ├── tests ├── CreatesApplication.php ├── Feature │ ├── ApiTokenPermissionsTest.php │ ├── AuthenticationTest.php │ ├── BrowserSessionsTest.php │ ├── CreateApiTokenTest.php │ ├── CreateTeamTest.php │ ├── DeleteAccountTest.php │ ├── DeleteApiTokenTest.php │ ├── DeleteTeamTest.php │ ├── EmailVerificationTest.php │ ├── ExampleTest.php │ ├── InviteTeamMemberTest.php │ ├── LeaveTeamTest.php │ ├── PasswordConfirmationTest.php │ ├── PasswordResetTest.php │ ├── ProfileInformationTest.php │ ├── RegistrationTest.php │ ├── RemoveTeamMemberTest.php │ ├── SocialstreamRegistrationTest.php │ ├── TwoFactorAuthenticationSettingsTest.php │ ├── UpdatePasswordTest.php │ ├── UpdateTeamMemberRoleTest.php │ └── UpdateTeamNameTest.php ├── TestCase.php └── Unit │ ├── ExampleTest.php │ └── Services │ └── BillingServiceTest.php └── vite.config.js /.docker/config/config/php.ini: -------------------------------------------------------------------------------- 1 | [Date] 2 | date.timezone="UTC" 3 | expose_php= Off -------------------------------------------------------------------------------- /.docker/config/config/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | logfile=/dev/null 4 | logfile_maxbytes=0 5 | pidfile=/run/supervisord.pid 6 | 7 | [program:php-fpm] 8 | command=php-fpm83 -F 9 | stdout_logfile=/dev/stdout 10 | stdout_logfile_maxbytes=0 11 | stderr_logfile=/dev/stderr 12 | stderr_logfile_maxbytes=0 13 | autorestart=false 14 | startretries=0 15 | 16 | [program:nginx] 17 | command=nginx -g 'daemon off;' 18 | stdout_logfile=/dev/stdout 19 | stdout_logfile_maxbytes=0 20 | stderr_logfile=/dev/stderr 21 | stderr_logfile_maxbytes=0 22 | autorestart=false 23 | startretries=0 24 | -------------------------------------------------------------------------------- /.docker/config/php.ini: -------------------------------------------------------------------------------- 1 | [Date] 2 | date.timezone="UTC" 3 | expose_php= Off -------------------------------------------------------------------------------- /.docker/config/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | logfile=/dev/null 4 | logfile_maxbytes=0 5 | pidfile=/run/supervisord.pid 6 | 7 | [program:php-fpm] 8 | command=php-fpm83 -F 9 | stdout_logfile=/dev/stdout 10 | stdout_logfile_maxbytes=0 11 | stderr_logfile=/dev/stderr 12 | stderr_logfile_maxbytes=0 13 | autorestart=false 14 | startretries=0 15 | 16 | [program:nginx] 17 | command=nginx -g 'daemon off;' 18 | stdout_logfile=/dev/stdout 19 | stdout_logfile_maxbytes=0 20 | stderr_logfile=/dev/stderr 21 | stderr_logfile_maxbytes=0 22 | autorestart=false 23 | startretries=0 24 | -------------------------------------------------------------------------------- /.docker/octane/.rr.prod.yaml: -------------------------------------------------------------------------------- 1 | version: '2.7' 2 | rpc: 3 | listen: 'tcp://127.0.0.1:6001' 4 | http: 5 | middleware: [ "static", "gzip", "headers" ] 6 | max_request_size: 20 7 | static: 8 | dir: "public" 9 | forbid: [ ".php", ".htaccess" ] 10 | uploads: 11 | forbid: [".php", ".exe", ".bat", ".sh"] 12 | pool: 13 | allocate_timeout: 10s 14 | destroy_timeout: 10s 15 | supervisor: 16 | max_worker_memory: 128 17 | exec_ttl: 60s 18 | logs: 19 | mode: production 20 | level: debug 21 | encoding: json 22 | -------------------------------------------------------------------------------- /.docker/octane/RoadRunner/.rr.prod.yaml: -------------------------------------------------------------------------------- 1 | version: '2.7' 2 | rpc: 3 | listen: 'tcp://127.0.0.1:6001' 4 | server: 5 | relay: pipes 6 | http: 7 | middleware: [ "static", "gzip", "headers" ] 8 | max_request_size: 20 9 | static: 10 | dir: "public" 11 | forbid: [ ".php", ".htaccess" ] 12 | uploads: 13 | forbid: [".php", ".exe", ".bat", ".sh"] 14 | pool: 15 | allocate_timeout: 10s 16 | destroy_timeout: 10s 17 | supervisor: 18 | max_worker_memory: 128 19 | exec_ttl: 60s 20 | logs: 21 | mode: production 22 | level: debug 23 | encoding: json 24 | status: 25 | address: localhost:2114 26 | -------------------------------------------------------------------------------- /.docker/octane/opcache.ini: -------------------------------------------------------------------------------- 1 | [Opcache] 2 | opcache.enable = 1 3 | opcache.enable_cli = 1 4 | opcache.memory_consumption = 256M 5 | opcache.use_cwd = 0 6 | opcache.max_file_size = 0 7 | opcache.max_accelerated_files = 32531 8 | opcache.validate_timestamps = 0 9 | opcache.revalidate_freq = 0 10 | 11 | [JIT] 12 | opcache.jit_buffer_size = 100M 13 | opcache.jit = function -------------------------------------------------------------------------------- /.docker/octane/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | post_max_size = 100M 3 | upload_max_filesize = 100M 4 | expose_php = 0 5 | variables_order = "GPCS" 6 | -------------------------------------------------------------------------------- /.docker/octane/supervisord.app.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=root 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [program:octane] 8 | process_name=%(program_name)s_%(process_num)02d 9 | command=php /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port=9000 --workers=auto --task-workers=auto --max-requests=500 10 | user=octane 11 | autostart=true 12 | autorestart=true 13 | environment=LARAVEL_OCTANE="1" 14 | stdout_logfile=/dev/stdout 15 | stdout_logfile_maxbytes=0 16 | stderr_logfile=/dev/stderr 17 | stderr_logfile_maxbytes=0 18 | 19 | [program:horizon] 20 | process_name=%(program_name)s_%(process_num)02d 21 | command=php /var/www/html/artisan horizon 22 | user=octane 23 | autostart=%(ENV_APP_WITH_HORIZON)s 24 | autorestart=true 25 | stdout_logfile=/var/www/html/horizon.log 26 | stopwaitsecs=3600 27 | 28 | [program:scheduler] 29 | process_name=%(program_name)s_%(process_num)02d 30 | command=supercronic /etc/supercronic/laravel 31 | user=octane 32 | autostart=%(ENV_APP_WITH_SCHEDULER)s 33 | autorestart=true 34 | stdout_logfile=/var/www/html/scheduler.log -------------------------------------------------------------------------------- /.docker/octane/supervisord.app.roadrunner.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=root 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [program:octane] 8 | process_name=%(program_name)s_%(process_num)02d 9 | command=php /var/www/html/artisan octane:start --server=roadrunner --host=0.0.0.0 --port=9000 --rpc-port=6001 --workers=auto --max-requests=500 --rr-config=/var/www/html/.rr.yaml 10 | user=octane 11 | autostart=true 12 | autorestart=true 13 | environment=LARAVEL_OCTANE="1" 14 | stdout_logfile=/dev/stdout 15 | stdout_logfile_maxbytes=0 16 | stderr_logfile=/dev/stderr 17 | stderr_logfile_maxbytes=0 18 | 19 | [program:horizon] 20 | process_name=%(program_name)s_%(process_num)02d 21 | command=php /var/www/html/artisan horizon 22 | user=octane 23 | autostart=%(ENV_APP_WITH_HORIZON)s 24 | autorestart=true 25 | stdout_logfile=/var/www/html/horizon.log 26 | stopwaitsecs=3600 27 | 28 | [program:scheduler] 29 | process_name=%(program_name)s_%(process_num)02d 30 | command=supercronic /etc/supercronic/laravel 31 | user=octane 32 | autostart=%(ENV_APP_WITH_SCHEDULER)s 33 | autorestart=true 34 | stdout_logfile=/var/www/html/scheduler.log 35 | -------------------------------------------------------------------------------- /.docker/octane/supervisord.horizon.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=root 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [program:horizon] 8 | process_name=%(program_name)s_%(process_num)02d 9 | command=php /var/www/html/artisan horizon 10 | user=octane 11 | autostart=true 12 | autorestart=true 13 | stdout_logfile=/dev/stdout 14 | stdout_logfile_maxbytes=0 15 | stderr_logfile=/dev/stderr 16 | stderr_logfile_maxbytes=0 17 | stopwaitsecs=3600 18 | -------------------------------------------------------------------------------- /.docker/octane/utilities.sh: -------------------------------------------------------------------------------- 1 | php() { 2 | echo "Running PHP as octane user ..." 3 | su octane -c "php $*" 4 | } 5 | 6 | tinker() { 7 | if [ -z "$1" ]; then 8 | php artisan tinker 9 | else 10 | php artisan tinker --execute="\"dd($1);\"" 11 | fi 12 | } 13 | 14 | # Determine size of a file or total size of a directory 15 | fs() { 16 | if du -b /dev/null >/dev/null 2>&1; then 17 | local arg=-sbh 18 | else 19 | local arg=-sh 20 | fi 21 | if [[ -n "$@" ]]; then 22 | du $arg -- "$@" 23 | else 24 | du $arg .[^.]* ./* 25 | fi 26 | } 27 | 28 | # Commonly used aliases 29 | alias ..="cd .." 30 | alias ...="cd ../.." 31 | alias art="php artisan" 32 | -------------------------------------------------------------------------------- /.docker/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | memory_limit = 512M 3 | post_max_size = 100M 4 | upload_max_filesize = 100M 5 | max_execution_time = 300 6 | max_input_time = 300 7 | expose_php = 0 8 | realpath_cache_size = 16M 9 | realpath_cache_ttl = 360 10 | zend.max_allowed_stack_size = 32M 11 | zend.reserved_stack_size = 2M 12 | 13 | [Opcache] 14 | opcache.enable = 1 15 | opcache.enable_cli = 1 16 | opcache.memory_consumption = 512M 17 | opcache.interned_strings_buffer = 32 18 | opcache.max_accelerated_files = 32531 19 | opcache.validate_timestamps = 0 20 | opcache.save_comments = 1 21 | opcache.fast_shutdown = 1 22 | 23 | [JIT] 24 | opcache.jit_buffer_size = 256M 25 | opcache.jit = 1255 26 | opcache.jit_prof_threshold = 0.001 27 | 28 | [zlib] 29 | zlib.output_compression = On 30 | zlib.output_compression_level = 9 31 | -------------------------------------------------------------------------------- /.docker/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=%(ENV_USER)s 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [unix_http_server] 8 | file=/var/run/supervisor.sock 9 | 10 | [supervisorctl] 11 | serverurl=unix:///var/run/supervisor.sock 12 | 13 | [rpcinterface:supervisor] 14 | supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface 15 | -------------------------------------------------------------------------------- /.docker/supervisord.horizon.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=%(ENV_USER)s 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [program:horizon] 8 | process_name=%(program_name)s_%(process_num)02d 9 | command=php %(ENV_ROOT)s/artisan horizon 10 | user=%(ENV_USER)s 11 | autostart=true 12 | autorestart=true 13 | stdout_logfile=/dev/stdout 14 | stdout_logfile_maxbytes=0 15 | stderr_logfile=/dev/stderr 16 | stderr_logfile_maxbytes=0 17 | stopwaitsecs=3600 18 | -------------------------------------------------------------------------------- /.docker/supervisord.scheduler.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=%(ENV_USER)s 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [program:scheduler] 8 | process_name=%(program_name)s_%(process_num)02d 9 | command=supercronic -overlapping /etc/supercronic/laravel 10 | user=%(ENV_USER)s 11 | autostart=%(ENV_WITH_SCHEDULER)s 12 | autorestart=true 13 | stdout_logfile=/dev/stdout 14 | stdout_logfile_maxbytes=0 15 | stderr_logfile=/dev/stderr 16 | stderr_logfile_maxbytes=0 17 | 18 | [program:clear-scheduler-cache] 19 | process_name=%(program_name)s_%(process_num)02d 20 | command=php %(ENV_ROOT)s/artisan schedule:clear-cache 21 | user=%(ENV_USER)s 22 | autostart=%(ENV_WITH_SCHEDULER)s 23 | autorestart=false 24 | stdout_logfile=/dev/stdout 25 | stdout_logfile_maxbytes=0 26 | stderr_logfile=/dev/stderr 27 | stderr_logfile_maxbytes=0 -------------------------------------------------------------------------------- /.docker/supervisord.worker.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=%(ENV_USER)s 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [program:worker] 8 | process_name=%(program_name)s_%(process_num)02d 9 | command=%(ENV_WORKER_COMMAND)s 10 | user=%(ENV_USER)s 11 | autostart=true 12 | autorestart=true 13 | stdout_logfile=/dev/stdout 14 | stdout_logfile_maxbytes=0 15 | stderr_logfile=/dev/stderr 16 | stderr_logfile_maxbytes=0 17 | -------------------------------------------------------------------------------- /.docker/utilities.sh: -------------------------------------------------------------------------------- 1 | tinker() { 2 | if [ -z "$1" ]; then 3 | php artisan tinker 4 | else 5 | php artisan tinker --execute="\"dd($1);\"" 6 | fi 7 | } 8 | 9 | # Commonly used aliases 10 | alias ..="cd .." 11 | alias ...="cd ../.." 12 | alias art="php artisan" 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: laravel-liberu 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "composer" 4 | # Files stored in repository root 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | - package-ecosystem: "npm" 9 | # Files stored in repository root 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | - package-ecosystem: "github-actions" 14 | # Workflow files stored in the 15 | # default location of `.github/workflows` 16 | directory: "/" 17 | schedule: 18 | interval: "daily" 19 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | This is a **bug | feature request**. 3 | 4 | 5 | ### Prerequisites 6 | * [ ] Are you running the latest version? 7 | * [ ] Are you reporting to the correct repository? 8 | * [ ] Did you check the documentation? 9 | * [ ] Did you perform a cursory search? 10 | 11 | ### Description 12 | 13 | 14 | ### Steps to Reproduce 15 | 20 | 21 | ### Expected behavior 22 | 23 | 24 | ### Actual behavior 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | 3 | 4 | 5 | 6 | node_modules/ 7 | npm-debug.log 8 | yarn-error.log 9 | 10 | # Laravel 4 specific 11 | bootstrap/compiled.php 12 | app/storage/ 13 | 14 | # Laravel 5 & Lumen specific 15 | public/storage 16 | public/hot 17 | public/build 18 | 19 | # Laravel 5 & Lumen specific with changed public path 20 | public_html/storage 21 | public_html/hot 22 | 23 | storage/*.key 24 | .env 25 | Homestead.yaml 26 | Homestead.json 27 | /.vagrant 28 | .phpunit.result.cache 29 | rr 30 | .rr.yaml 31 | -------------------------------------------------------------------------------- /app/Actions/Fortify/PasswordValidationRules.php: -------------------------------------------------------------------------------- 1 | |string> 13 | */ 14 | protected function passwordRules(): array 15 | { 16 | return ['required', 'string', Password::default(), 'confirmed']; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/CreateTeam.php: -------------------------------------------------------------------------------- 1 | $input 19 | */ 20 | public function create(User $user, array $input): Team 21 | { 22 | Gate::forUser($user)->authorize('create', Jetstream::newTeamModel()); 23 | 24 | Validator::make($input, [ 25 | 'name' => ['required', 'string', 'max:255'], 26 | ])->validateWithBag('createTeam'); 27 | 28 | AddingTeam::dispatch($user); 29 | 30 | $user->switchTeam($team = $user->ownedTeams()->create([ 31 | 'name' => $input['name'], 32 | 'personal_team' => true, 33 | ])); 34 | 35 | return $team; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/DeleteTeam.php: -------------------------------------------------------------------------------- 1 | purge(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/DeleteUser.php: -------------------------------------------------------------------------------- 1 | deleteProfilePhoto(); 18 | $user->tokens->each->delete(); 19 | $user->connectedAccounts->each->delete(); 20 | $user->delete(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/DeleteUserWithTeams.php: -------------------------------------------------------------------------------- 1 | deleteTeams($user); 27 | $user->deleteProfilePhoto(); 28 | $user->tokens->each->delete(); 29 | $user->delete(); 30 | }); 31 | } 32 | 33 | /** 34 | * Delete the teams and team associations attached to the user. 35 | */ 36 | protected function deleteTeams(User $user): void 37 | { 38 | $user->teams()->detach(); 39 | 40 | $user->ownedTeams->each(function (Team $team) { 41 | $this->deletesTeams->delete($team); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/UpdateTeamName.php: -------------------------------------------------------------------------------- 1 | $input 17 | */ 18 | public function update(User $user, Team $team, array $input): void 19 | { 20 | Gate::forUser($user)->authorize('update', $team); 21 | 22 | Validator::make($input, [ 23 | 'name' => ['required', 'string', 'max:255'], 24 | ])->validateWithBag('updateTeamName'); 25 | 26 | $team->forceFill([ 27 | 'name' => $input['name'], 28 | ])->save(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Actions/Socialstream/GenerateRedirectForProvider.php: -------------------------------------------------------------------------------- 1 | redirect(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Actions/Socialstream/HandleInvalidState.php: -------------------------------------------------------------------------------- 1 | user(); 18 | 19 | if (Socialstream::generatesMissingEmails()) { 20 | $user->email = $user->getEmail() ?? ("{$user->id}@{$provider}".config('app.domain')); 21 | } 22 | 23 | return $user; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Actions/Socialstream/SetUserPassword.php: -------------------------------------------------------------------------------- 1 | ['required', 'string', Password::default(), 'confirmed'], 19 | ])->validateWithBag('setPassword'); 20 | 21 | $user->forceFill([ 22 | 'password' => Hash::make($input['password']), 23 | ])->save(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Console/Commands/PruneAuditLogs.php: -------------------------------------------------------------------------------- 1 | option('days'); 16 | $count = AuditLog::where('created_at', '<', now()->subDays($days))->delete(); 17 | 18 | $this->info("Deleted {$count} audit logs older than {$days} days."); 19 | } 20 | } -------------------------------------------------------------------------------- /app/Console/Commands/SendInvoiceReminders.php: -------------------------------------------------------------------------------- 1 | info('Sending invoice reminders...'); 16 | 17 | try { 18 | $billingService->sendUpcomingDueReminders(); 19 | $this->info('Upcoming due date reminders sent successfully.'); 20 | 21 | $billingService->sendOverdueReminders(); 22 | $this->info('Overdue reminders sent successfully.'); 23 | 24 | } catch (\Exception $e) { 25 | $this->error('Error sending reminders: ' . $e->getMessage()); 26 | return 1; 27 | } 28 | 29 | return 0; 30 | } 31 | } -------------------------------------------------------------------------------- /app/Console/Commands/SyncEnomDomains.php: -------------------------------------------------------------------------------- 1 | domainPricingService = $domainPricingService; 20 | } 21 | 22 | public function handle() 23 | { 24 | $this->info('Starting Enom domain synchronization...'); 25 | 26 | try { 27 | $this->domainPricingService->syncTldsFromEnom(); 28 | $this->info('Enom domain synchronization completed successfully.'); 29 | } catch (\Exception $e) { 30 | $this->error('An error occurred during Enom domain synchronization: ' . $e->getMessage()); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/Events/InvoiceStatusChanged.php: -------------------------------------------------------------------------------- 1 | invoice = $invoice; 22 | $this->status = $status; 23 | } 24 | 25 | public function broadcastOn() 26 | { 27 | return new Channel('invoices.' . $this->invoice->id); 28 | } 29 | 30 | public function broadcastWith() 31 | { 32 | return [ 33 | 'invoice_id' => $this->invoice->id, 34 | 'status' => $this->status, 35 | 'timestamp' => now()->toIso8601String(), 36 | ]; 37 | } 38 | } -------------------------------------------------------------------------------- /app/Filament/Admin/Resources/MenuResource/Pages/CreateMenu.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 16 | } 17 | } -------------------------------------------------------------------------------- /app/Filament/App/Resources/RefundResource/Pages/EditRefund.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 23 | } 24 | } -------------------------------------------------------------------------------- /app/Filament/App/Resources/RefundResource/Pages/ListRefunds.php: -------------------------------------------------------------------------------- 1 | validate(['email' => 'required|email']); 19 | 20 | $status = Password::broker('admins')->sendResetLink( 21 | $request->only('email') 22 | ); 23 | 24 | return $status === Password::RESET_LINK_SENT 25 | ? back()->with(['status' => __($status)]) 26 | : back()->withErrors(['email' => __($status)]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Controllers/PaymentHistoryController.php: -------------------------------------------------------------------------------- 1 | has('customer_id')) { 16 | $query->where('customer_id', $request->customer_id); 17 | } 18 | 19 | $paymentHistories = $query->latest()->paginate(10); 20 | 21 | return view('payment-history.index', compact('paymentHistories')); 22 | } 23 | 24 | public function customerHistory($customerId) 25 | { 26 | $customer = Customer::findOrFail($customerId); 27 | $paymentHistories = PaymentHistory::where('customer_id', $customerId) 28 | ->with(['payment', 'invoice']) 29 | ->latest() 30 | ->paginate(10); 31 | 32 | return view('payment-history.customer', compact('customer', 'paymentHistories')); 33 | } 34 | } -------------------------------------------------------------------------------- /app/Http/Livewire/CreateTeam.php: -------------------------------------------------------------------------------- 1 | validate(); 19 | 20 | $team = app(CreateTeam::class)->create( 21 | Auth::user(), 22 | ['name' => $this->state['name']] 23 | ); 24 | 25 | return redirect()->route('filament.pages.edit-team', ['team' => $team]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Middleware/AssignDefaultTeam.php: -------------------------------------------------------------------------------- 1 | check()) { 15 | $user = auth()->user(); 16 | $defaultTeam = $user->currentTeam ?? $user->ownedTeams()->first(); 17 | if (!$defaultTeam) { 18 | $defaultTeam = $user->ownedTeams()->create([ 19 | 'name' => $user->name . "'s Team", 20 | 'personal_team' => true, 21 | ]); 22 | $user->current_team_id = $defaultTeam->id; 23 | $user->save(); 24 | } 25 | Filament::setTenant($defaultTeam); 26 | } 27 | return $next($request); 28 | } 29 | } -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson() ? null : route('login'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/CheckRole.php: -------------------------------------------------------------------------------- 1 | user() || !$request->user()->hasAnyRole($roles)) { 13 | abort(403, 'Unauthorized action.'); 14 | } 15 | 16 | return $next($request); 17 | } 18 | } -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/PreventRequestsDuringMaintenance.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/RequireTwoFactorEnabled.php: -------------------------------------------------------------------------------- 1 | user() || !$request->user()->two_factor_secret) { 19 | return redirect()->route('profile.show') 20 | ->with('error', 'Two-factor authentication must be enabled to access this area.'); 21 | } 22 | 23 | return $next($request); 24 | } 25 | } -------------------------------------------------------------------------------- /app/Http/Middleware/ScreeningDataEncryptor.php: -------------------------------------------------------------------------------- 1 | getContent()) { 15 | $content = json_decode($response->getContent(), true); 16 | 17 | $fieldsToEncrypt = [ 18 | 'background_check_status', 19 | 'credit_report_status', 20 | 'rental_history_status' 21 | ]; 22 | 23 | foreach ($fieldsToEncrypt as $field) { 24 | if (isset($content[$field])) { 25 | $content[$field] = Crypt::encryptString($content[$field]); 26 | } 27 | } 28 | 29 | $response->setContent(json_encode($content)); 30 | } 31 | 32 | return $response; 33 | } 34 | } -------------------------------------------------------------------------------- /app/Http/Middleware/TeamsPermission.php: -------------------------------------------------------------------------------- 1 | user()) && !empty($user->current_team_id)) { 15 | app(PermissionRegistrar::class)->setPermissionsTeamId($user->current_team_id); 16 | } 17 | 18 | return $next($request); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | 'current_password', 16 | 'password', 17 | 'password_confirmation', 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustHosts.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | public function hosts(): array 15 | { 16 | return [ 17 | $this->allSubdomainsOfApplicationUrl(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | |string|null 14 | */ 15 | protected $proxies; 16 | 17 | /** 18 | * The headers that should be used to detect proxies. 19 | * 20 | * @var int 21 | */ 22 | protected $headers = 23 | Request::HEADER_X_FORWARDED_FOR | 24 | Request::HEADER_X_FORWARDED_HOST | 25 | Request::HEADER_X_FORWARDED_PORT | 26 | Request::HEADER_X_FORWARDED_PROTO | 27 | Request::HEADER_X_FORWARDED_AWS_ELB; 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Middleware/ValidateSignature.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 'fbclid', 16 | // 'utm_campaign', 17 | // 'utm_content', 18 | // 'utm_medium', 19 | // 'utm_source', 20 | // 'utm_term', 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Jobs/ProcessSubscriptionBilling.php: -------------------------------------------------------------------------------- 1 | where('status', 'active') 21 | ->where('auto_renew', true) 22 | ->get() 23 | ->each(function ($subscription) use ($billingService) { 24 | if ($subscription->needsBilling()) { 25 | $invoice = $billingService->generateInvoice($subscription); 26 | $billingService->processAutomaticPayment($invoice); 27 | $subscription->renew(); 28 | } 29 | }); 30 | } 31 | } -------------------------------------------------------------------------------- /app/Listeners/CreatePersonalTeam.php: -------------------------------------------------------------------------------- 1 | teamManagementService = $teamManagementService; 15 | } 16 | 17 | public function handle(Registered $event): void 18 | { 19 | $this->teamManagementService->assignUserToDefaultTeam($event->user); 20 | } 21 | } -------------------------------------------------------------------------------- /app/Listeners/EmailTracker.php: -------------------------------------------------------------------------------- 1 | message; 14 | $campaignId = $message->getHeaders()->get('X-Campaign-ID'); 15 | $leadId = $message->getHeaders()->get('X-Lead-ID'); 16 | 17 | if ($campaignId && $leadId) { 18 | $campaign = EmailCampaign::find($campaignId); 19 | $lead = Lead::find($leadId); 20 | 21 | if ($campaign && $lead) { 22 | // Record that the email was sent 23 | $campaign->emailStats()->create([ 24 | 'lead_id' => $lead->id, 25 | 'sent_at' => now(), 26 | ]); 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /app/Listeners/SwitchTeam.php: -------------------------------------------------------------------------------- 1 | invoice = $invoice; 22 | } 23 | 24 | public function envelope(): Envelope 25 | { 26 | return new Envelope( 27 | subject: 'Overdue Invoice Reminder', 28 | ); 29 | } 30 | 31 | public function content(): Content 32 | { 33 | return new Content( 34 | view: 'emails.overdue-invoice-reminder', 35 | ); 36 | } 37 | 38 | public function attachments(): array 39 | { 40 | return []; 41 | } 42 | } -------------------------------------------------------------------------------- /app/Mail/PaymentConfirmation.php: -------------------------------------------------------------------------------- 1 | payment = $payment; 21 | } 22 | 23 | public function envelope(): Envelope 24 | { 25 | return new Envelope( 26 | subject: 'Payment Confirmation - Invoice #' . $this->payment->invoice->invoice_number, 27 | ); 28 | } 29 | 30 | public function content(): Content 31 | { 32 | return new Content( 33 | view: 'emails.payment-confirmation', 34 | ); 35 | } 36 | 37 | public function attachments(): array 38 | { 39 | return []; 40 | } 41 | } -------------------------------------------------------------------------------- /app/Mail/UpcomingInvoiceReminder.php: -------------------------------------------------------------------------------- 1 | data = $data; 19 | $this->template = $template; 20 | } 21 | 22 | public function build() 23 | { 24 | return $this->subject($this->parseTemplate($this->template->subject)) 25 | ->view('emails.upcoming-invoice-reminder') 26 | ->with([ 27 | 'content' => $this->parseTemplate($this->template->body), 28 | 'data' => $this->data 29 | ]); 30 | } 31 | 32 | private function parseTemplate($text) 33 | { 34 | foreach ($this->data as $key => $value) { 35 | $text = str_replace('{{'.$key.'}}', $value, $text); 36 | } 37 | return $text; 38 | } 39 | } -------------------------------------------------------------------------------- /app/Models/AuditLog.php: -------------------------------------------------------------------------------- 1 | 'array', 24 | 'new_values' => 'array', 25 | ]; 26 | 27 | public function user(): BelongsTo 28 | { 29 | return $this->belongsTo(User::class); 30 | } 31 | 32 | public function auditable(): MorphTo 33 | { 34 | return $this->morphTo(); 35 | } 36 | } -------------------------------------------------------------------------------- /app/Models/Client.php: -------------------------------------------------------------------------------- 1 | hasMany(ClientNote::class); 26 | } 27 | 28 | 29 | protected $casts = [ 30 | 'created_at' => 'datetime', 31 | 'updated_at' => 'datetime', 32 | ]; 33 | } -------------------------------------------------------------------------------- /app/Models/ClientNote.php: -------------------------------------------------------------------------------- 1 | belongsTo(Client::class); 15 | } 16 | 17 | public function user(): BelongsTo 18 | { 19 | return $this->belongsTo(User::class); 20 | } 21 | } -------------------------------------------------------------------------------- /app/Models/Credit.php: -------------------------------------------------------------------------------- 1 | belongsTo(Customer::class); 24 | } 25 | } -------------------------------------------------------------------------------- /app/Models/Currency.php: -------------------------------------------------------------------------------- 1 | hasMany(Invoice::class, 'currency', 'code'); 21 | } 22 | 23 | public function invoiceItems() 24 | { 25 | return $this->hasMany(Invoice_Item::class, 'currency', 'code'); 26 | } 27 | 28 | public function payments() 29 | { 30 | return $this->hasMany(Payment::class, 'currency', 'code'); 31 | } 32 | } -------------------------------------------------------------------------------- /app/Models/Customer.php: -------------------------------------------------------------------------------- 1 | hasMany(Credit::class); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Models/Discount.php: -------------------------------------------------------------------------------- 1 | 'datetime', 30 | 'end_date' => 'datetime', 31 | 'is_active' => 'boolean' 32 | ]; 33 | 34 | public function invoices() 35 | { 36 | return $this->hasMany(Invoice::class); 37 | } 38 | 39 | public function isValid() 40 | { 41 | return $this->is_active && 42 | $this->start_date <= now() && 43 | $this->end_date >= now() && 44 | ($this->max_uses === null || $this->used_count < $this->max_uses); 45 | } 46 | } -------------------------------------------------------------------------------- /app/Models/EmailTemplate.php: -------------------------------------------------------------------------------- 1 | where(function ($query) use ($teamId) { 26 | $query->where('team_id', $teamId) 27 | ->orWhere('is_default', true); 28 | }) 29 | ->orderBy('team_id', 'desc') // Prioritize team templates 30 | ->first(); 31 | } 32 | } -------------------------------------------------------------------------------- /app/Models/File.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 24 | } 25 | 26 | public function folder(): BelongsTo 27 | { 28 | return $this->belongsTo(Folder::class); 29 | } 30 | 31 | public function team(): BelongsTo 32 | { 33 | return $this->belongsTo(Team::class); 34 | } 35 | 36 | public function shares(): HasMany 37 | { 38 | return $this->hasMany(FileShare::class); 39 | } 40 | } -------------------------------------------------------------------------------- /app/Models/Folder.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 21 | } 22 | 23 | public function parent(): BelongsTo 24 | { 25 | return $this->belongsTo(Folder::class, 'parent_id'); 26 | } 27 | 28 | public function children(): HasMany 29 | { 30 | return $this->hasMany(Folder::class, 'parent_id'); 31 | } 32 | 33 | public function files(): HasMany 34 | { 35 | return $this->hasMany(File::class); 36 | } 37 | 38 | public function team(): BelongsTo 39 | { 40 | return $this->belongsTo(Team::class); 41 | } 42 | } -------------------------------------------------------------------------------- /app/Models/HostingAccount.php: -------------------------------------------------------------------------------- 1 | belongsTo(Customer::class); 27 | } 28 | 29 | public function subscription() 30 | { 31 | return $this->belongsTo(Subscription::class); 32 | } 33 | 34 | public function isActive() 35 | { 36 | return $this->status === 'active'; 37 | } 38 | 39 | public function hasDomain() 40 | { 41 | return !empty($this->domain); 42 | } 43 | } -------------------------------------------------------------------------------- /app/Models/Integration.php: -------------------------------------------------------------------------------- 1 | 'datetime', 22 | 'settings' => 'array', 23 | 'scopes' => 'array', 24 | ]; 25 | 26 | protected $hidden = [ 27 | 'token', 28 | 'refresh_token', 29 | ]; 30 | 31 | public function user(): BelongsTo 32 | { 33 | return $this->belongsTo(User::class); 34 | } 35 | } -------------------------------------------------------------------------------- /app/Models/InvoiceDispute.php: -------------------------------------------------------------------------------- 1 | 'datetime' 27 | ]; 28 | 29 | public function invoice() 30 | { 31 | return $this->belongsTo(Invoice::class); 32 | } 33 | 34 | public function customer() 35 | { 36 | return $this->belongsTo(Customer::class); 37 | } 38 | 39 | public function resolver() 40 | { 41 | return $this->belongsTo(User::class, 'resolved_by'); 42 | } 43 | 44 | public function messages() 45 | { 46 | return $this->hasMany(DisputeMessage::class); 47 | } 48 | } -------------------------------------------------------------------------------- /app/Models/Invoice_Item.php: -------------------------------------------------------------------------------- 1 | belongsTo(Currency::class, 'currency', 'code'); 26 | } 27 | 28 | public function invoice() 29 | { 30 | return $this->belongsTo(Invoice::class); 31 | } 32 | 33 | public function productService() 34 | { 35 | return $this->belongsTo(Products_Service::class, 'product_service_id'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Models/Membership.php: -------------------------------------------------------------------------------- 1 | belongsTo(Menu::class, 'parent_id'); 22 | } 23 | 24 | public function children() 25 | { 26 | return $this->hasMany(Menu::class, 'parent_id'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Models/PaymentGateway.php: -------------------------------------------------------------------------------- 1 | 'boolean', 23 | ]; 24 | 25 | public function payments() 26 | { 27 | return $this->hasMany(Payment::class); 28 | } 29 | } -------------------------------------------------------------------------------- /app/Models/PaymentHistory.php: -------------------------------------------------------------------------------- 1 | belongsTo(Payment::class); 29 | } 30 | 31 | public function invoice() 32 | { 33 | return $this->belongsTo(Invoice::class); 34 | } 35 | 36 | public function customer() 37 | { 38 | return $this->belongsTo(Customer::class); 39 | } 40 | } -------------------------------------------------------------------------------- /app/Models/PaymentPlan.php: -------------------------------------------------------------------------------- 1 | 'datetime', 26 | 'next_due_date' => 'datetime', 27 | ]; 28 | 29 | public function invoice() 30 | { 31 | return $this->belongsTo(Invoice::class); 32 | } 33 | 34 | public function installments() 35 | { 36 | return $this->hasMany(Invoice::class, 'parent_invoice_id'); 37 | } 38 | } -------------------------------------------------------------------------------- /app/Models/ReminderSetting.php: -------------------------------------------------------------------------------- 1 | 'boolean', 23 | ]; 24 | } -------------------------------------------------------------------------------- /app/Models/Report.php: -------------------------------------------------------------------------------- 1 | 'datetime', 29 | 'end_date' => 'datetime', 30 | 'filters' => 'array' 31 | 'parameters' => 'array', 32 | 'schedule' => 'array', 33 | 'last_generated_at' => 'datetime' 34 | ]; 35 | 36 | public function team() 37 | { 38 | return $this->belongsTo(Team::class); 39 | } 40 | } -------------------------------------------------------------------------------- /app/Models/Role.php: -------------------------------------------------------------------------------- 1 | belongsTo(Team::class); 16 | } 17 | 18 | public function hasPermissionTo($permission, $guardName = null): bool 19 | { 20 | return $this->permissions()->where('name', $permission)->exists() ?? false; 21 | } 22 | 23 | public static function defaultRoles(): array 24 | { 25 | return [ 26 | 'super_admin' => 'Full access to all features', 27 | 'admin' => 'Administrative access with some restrictions', 28 | 'staff' => 'Standard staff access', 29 | 'client' => 'Client access with limited permissions', 30 | 'free' => 'Basic access for free users' 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Models/SavedSearch.php: -------------------------------------------------------------------------------- 1 | 'array' 19 | ]; 20 | 21 | public function user(): BelongsTo 22 | { 23 | return $this->belongsTo(User::class); 24 | } 25 | } -------------------------------------------------------------------------------- /app/Models/SubscriptionPlan.php: -------------------------------------------------------------------------------- 1 | 'array', 25 | 'is_active' => 'boolean', 26 | 'trial_days' => 'integer', 27 | 'price' => 'decimal:2' 28 | ]; 29 | 30 | public function subscriptions() 31 | { 32 | return $this->hasMany(Subscription::class); 33 | } 34 | 35 | public function getFormattedPriceAttribute() 36 | { 37 | return number_format($this->price, 2) . ' ' . $this->currency; 38 | } 39 | } -------------------------------------------------------------------------------- /app/Models/TaxExemption.php: -------------------------------------------------------------------------------- 1 | 'date', 23 | 'is_active' => 'boolean' 24 | ]; 25 | 26 | public function customer() 27 | { 28 | return $this->belongsTo(Customer::class); 29 | } 30 | 31 | public function isValid() 32 | { 33 | return $this->is_active && 34 | ($this->expiry_date === null || $this->expiry_date->isFuture()); 35 | } 36 | } -------------------------------------------------------------------------------- /app/Models/TeamInvitation.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | protected $fillable = [ 17 | 'email', 18 | 'role', 19 | ]; 20 | 21 | /** 22 | * Get the team that the invitation belongs to. 23 | */ 24 | public function team(): BelongsTo 25 | { 26 | return $this->belongsTo(Jetstream::teamModel()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Models/Ticket.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 25 | } 26 | 27 | public function responses(): HasMany 28 | { 29 | return $this->hasMany(TicketResponse::class); 30 | } 31 | } -------------------------------------------------------------------------------- /app/Models/TicketResponse.php: -------------------------------------------------------------------------------- 1 | belongsTo(Ticket::class); 22 | } 23 | 24 | public function user(): BelongsTo 25 | { 26 | return $this->belongsTo(User::class); 27 | } 28 | } -------------------------------------------------------------------------------- /app/Models/Tld.php: -------------------------------------------------------------------------------- 1 | 'float', 22 | 'markup_value' => 'float', 23 | 'enom_cost' => 'float', 24 | ]; 25 | 26 | public function calculatePrice() 27 | { 28 | if ($this->markup_type === 'percentage') { 29 | return $this->enom_cost * (1 + $this->markup_value / 100); 30 | } elseif ($this->markup_type === 'fixed') { 31 | return $this->enom_cost + $this->markup_value; 32 | } 33 | return $this->base_price; 34 | } 35 | } -------------------------------------------------------------------------------- /app/Models/UsageRecord.php: -------------------------------------------------------------------------------- 1 | 'datetime', 22 | 'processed' => 'boolean', 23 | 'quantity' => 'decimal:2' 24 | ]; 25 | 26 | public function subscription() 27 | { 28 | return $this->belongsTo(Subscription::class); 29 | } 30 | } -------------------------------------------------------------------------------- /app/Notifications/NewTicketNotification.php: -------------------------------------------------------------------------------- 1 | ticket = $ticket; 20 | } 21 | 22 | public function via($notifiable): array 23 | { 24 | return ['mail']; 25 | } 26 | 27 | public function toMail($notifiable): MailMessage 28 | { 29 | return (new MailMessage) 30 | ->subject('New Support Ticket Created') 31 | ->greeting('Hello!') 32 | ->line('A new support ticket has been created.') 33 | ->line('Title: ' . $this->ticket->title) 34 | ->line('Priority: ' . ucfirst($this->ticket->priority)) 35 | ->action('View Ticket', url('/tickets/' . $this->ticket->id)) 36 | ->line('Thank you for using our application!'); 37 | } 38 | } -------------------------------------------------------------------------------- /app/Policies/FilePolicy.php: -------------------------------------------------------------------------------- 1 | id === $file->user_id 13 | || $file->shares()->where('user_id', $user->id)->exists(); 14 | } 15 | 16 | public function update(User $user, File $file) 17 | { 18 | return $user->id === $file->user_id 19 | || $file->shares()->where('user_id', $user->id) 20 | ->whereIn('permission', ['write', 'admin']) 21 | ->exists(); 22 | } 23 | 24 | public function delete(User $user, File $file) 25 | { 26 | return $user->id === $file->user_id 27 | || $file->shares()->where('user_id', $user->id) 28 | ->where('permission', 'admin') 29 | ->exists(); 30 | } 31 | } -------------------------------------------------------------------------------- /app/Policies/InvoiceTemplatePolicy.php: -------------------------------------------------------------------------------- 1 | currentTeam->id === $template->team_id; 21 | } 22 | 23 | public function create(User $user) 24 | { 25 | return true; 26 | } 27 | 28 | public function update(User $user, InvoiceTemplate $template) 29 | { 30 | return $user->currentTeam->id === $template->team_id; 31 | } 32 | 33 | public function delete(User $user, InvoiceTemplate $template) 34 | { 35 | return $user->currentTeam->id === $template->team_id; 36 | } 37 | } -------------------------------------------------------------------------------- /app/Policies/TicketPolicy.php: -------------------------------------------------------------------------------- 1 | isAdmin() || $ticket->user_id === $user->id; 13 | } 14 | 15 | public function update(User $user, Ticket $ticket): bool 16 | { 17 | return $user->isAdmin(); 18 | } 19 | 20 | public function respond(User $user, Ticket $ticket): bool 21 | { 22 | return $user->isAdmin() || $ticket->user_id === $user->id; 23 | } 24 | } -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected $policies = [ 16 | // 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | */ 22 | public function boot(): void 23 | { 24 | // 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | startOfDay(); 13 | $endDate = Carbon::parse($endDate)->endOfDay(); 14 | 15 | $transactions = $affiliate->transactions() 16 | ->whereBetween('created_at', [$startDate, $endDate]) 17 | ->get(); 18 | 19 | $totalEarnings = $transactions->where('type', 'commission')->sum('amount'); 20 | $totalReferrals = $affiliate->referrals()->whereBetween('created_at', [$startDate, $endDate])->count(); 21 | 22 | return [ 23 | 'affiliate' => $affiliate, 24 | 'start_date' => $startDate->toDateString(), 25 | 'end_date' => $endDate->toDateString(), 26 | 'total_earnings' => $totalEarnings, 27 | 'total_referrals' => $totalReferrals, 28 | 'transactions' => $transactions, 29 | ]; 30 | } 31 | } -------------------------------------------------------------------------------- /app/Services/AffiliateService.php: -------------------------------------------------------------------------------- 1 | invoice->customer->user; 13 | $referrer = $user->referrer; 14 | 15 | if ($referrer && $referrer->status === 'active') { 16 | $product = $payment->invoice->items->first()->product; 17 | $commissionRate = $referrer->getCommissionRate($product->id, $product->category_id); 18 | $commissionAmount = $payment->amount * ($commissionRate / 100); 19 | 20 | $payment->update([ 21 | 'affiliate_id' => $referrer->id, 22 | 'affiliate_commission' => $commissionAmount, 23 | ]); 24 | 25 | $referrer->increment('total_earnings', $commissionAmount); 26 | 27 | // Create a transaction record for the affiliate 28 | $referrer->transactions()->create([ 29 | 'amount' => $commissionAmount, 30 | 'type' => 'commission', 31 | 'description' => "Commission for payment #{$payment->id}", 32 | ]); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/Services/AuditLogService.php: -------------------------------------------------------------------------------- 1 | Auth::id(), 16 | 'event' => $event, 17 | 'auditable_type' => $model ? get_class($model) : null, 18 | 'auditable_id' => $model?->id, 19 | 'old_values' => $oldValues, 20 | 'new_values' => $newValues, 21 | 'ip_address' => Request::ip(), 22 | 'user_agent' => Request::userAgent(), 23 | ]); 24 | } 25 | } -------------------------------------------------------------------------------- /app/Services/SiteSettingsService.php: -------------------------------------------------------------------------------- 1 | $key : $settings; 17 | } 18 | 19 | public function clear() 20 | { 21 | Cache::forget(config('site-settings.cache_key')); 22 | } 23 | } -------------------------------------------------------------------------------- /app/Traits/HasTeam.php: -------------------------------------------------------------------------------- 1 | belongsTo(Team::class); 13 | } 14 | } -------------------------------------------------------------------------------- /app/View/Components/AppLayout.php: -------------------------------------------------------------------------------- 1 | ['api/*', 'sanctum/csrf-cookie'], 19 | 20 | 'allowed_methods' => ['*'], 21 | 22 | 'allowed_origins' => ['*'], 23 | 24 | 'allowed_origins_patterns' => [], 25 | 26 | 'allowed_headers' => ['*'], 27 | 28 | 'exposed_headers' => [], 29 | 30 | 'max_age' => 0, 31 | 32 | 'supports_credentials' => false, 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /config/socialstream.php: -------------------------------------------------------------------------------- 1 | ['web'], 7 | 'prompt' => 'Or Login Via', 8 | 'providers' => [ 9 | // Providers::github(), 10 | ], 11 | 'component' => 'socialstream::components.socialstream', 12 | ]; 13 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => env( 32 | 'VIEW_COMPILED_PATH', 33 | realpath(storage_path('framework/views')) 34 | ), 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /database/factories/ConnectedAccountFactory.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class ConnectedAccountFactory extends Factory 14 | { 15 | protected $model = ConnectedAccount::class; 16 | 17 | public function definition(): array 18 | { 19 | return [ 20 | 'provider' => $this->faker->randomElement(Providers::all()), 21 | 'provider_id' => $this->faker->numerify('########'), 22 | 'token' => Str::random(432), 23 | 'refresh_token' => Str::random(432), 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /database/factories/CustomerFactory.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class CustomerFactory extends Factory 11 | { 12 | /** 13 | * Define the model's default state. 14 | * 15 | * @return array 16 | */ 17 | public function definition(): array 18 | { 19 | return [ 20 | 'name' => $this->faker->name, 21 | 'email' => $this->faker->unique()->safeEmail, 22 | 'phone_number' => $this->faker->phoneNumber, 23 | 'address' => $this->faker->address, 24 | 'city' => $this->faker->city, 25 | 'state' => $this->faker->state, 26 | 'postal_code' => $this->faker->postcode, 27 | 'country' => $this->faker->country, 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/factories/InvoiceFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class InvoiceFactory extends Factory 12 | { 13 | /** 14 | * Define the model's default state. 15 | * 16 | * @return array 17 | */ 18 | public function definition(): array 19 | { 20 | return [ 21 | 'customer_id' => Customer::factory(), 22 | 'invoice_number' => $this->faker->unique()->numerify('INV-#####'), 23 | 'issue_date' => $this->faker->date(), 24 | 'due_date' => $this->faker->date(), 25 | 'total_amount' => $this->faker->randomFloat(2, 100, 10000), 26 | 'status' => $this->faker->randomElement(['pending', 'paid', 'overdue']), 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/factories/InvoiceItemFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class InvoiceItemFactory extends Factory 13 | { 14 | /** 15 | * Define the model's default state. 16 | * 17 | * @return array 18 | */ 19 | public function definition(): array 20 | { 21 | $unitPrice = $this->faker->randomFloat(2, 1, 1000); 22 | $quantity = $this->faker->numberBetween(1, 10); 23 | $totalPrice = $unitPrice * $quantity; 24 | 25 | return [ 26 | 'invoice_id' => Invoice::factory(), 27 | 'product_service_id' => Products_Service::factory(), 28 | 'quantity' => $quantity, 29 | 'unit_price' => $unitPrice, 30 | 'total_price' => $totalPrice, 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/factories/MenuFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->word, 16 | 'url' => $this->faker->url, 17 | 'order' => $this->faker->numberBetween(1, 10), 18 | ]; 19 | } 20 | } -------------------------------------------------------------------------------- /database/factories/PaymentFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class PaymentFactory extends Factory 12 | { 13 | /** 14 | * Define the model's default state. 15 | * 16 | * @return array 17 | */ 18 | public function definition(): array 19 | { 20 | return [ 21 | 'invoice_id' => Invoice::factory(), 22 | 'payment_date' => $this->faker->date(), 23 | 'amount' => $this->faker->randomFloat(2, 10, 10000), 24 | 'payment_method' => $this->faker->randomElement(['credit card', 'bank transfer', 'PayPal']), 25 | 'transaction_id' => $this->faker->unique()->uuid(), 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database/factories/ProductsServiceFactory.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ProductsServiceFactory extends Factory 11 | { 12 | /** 13 | * Define the model's default state. 14 | * 15 | * @return array 16 | */ 17 | public function definition(): array 18 | { 19 | return [ 20 | 'name' => $this->faker->word, 21 | 'description' => $this->faker->paragraph, 22 | 'price' => $this->faker->randomFloat(2, 1, 1000), 23 | 'type' => $this->faker->randomElement(['product', 'service']), 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /database/factories/SubscriptionFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class SubscriptionFactory extends Factory 13 | { 14 | /** 15 | * Define the model's default state. 16 | * 17 | * @return array 18 | */ 19 | public function definition(): array 20 | { 21 | return [ 22 | 'customer_id' => Customer::factory(), 23 | 'product_service_id' => Products_Service::factory(), 24 | 'start_date' => $this->faker->date(), 25 | 'end_date' => $this->faker->optional()->date(), 26 | 'renewal_period' => $this->faker->randomElement(['monthly', 'yearly']), 27 | 'status' => $this->faker->randomElement(['active', 'cancelled', 'expired']), 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/factories/TeamFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class TeamFactory extends Factory 12 | { 13 | /** 14 | * Define the model's default state. 15 | * 16 | * @return array 17 | */ 18 | public function definition(): array 19 | { 20 | return [ 21 | 'name' => $this->faker->unique()->company(), 22 | 'user_id' => User::factory(), 23 | 'personal_team' => true, 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /database/migrations/2019_08_19_000000_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('uuid')->unique(); 17 | $table->text('connection'); 18 | $table->text('queue'); 19 | $table->longText('payload'); 20 | $table->longText('exception'); 21 | $table->timestamp('failed_at')->useCurrent(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('failed_jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2020_05_21_100000_create_teams_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->foreignId('user_id')->index(); 16 | $table->string('name'); 17 | $table->boolean('personal_team'); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::dropIfExists('teams'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2020_05_21_200000_create_team_user_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->foreignId('team_id'); 16 | $table->foreignId('user_id'); 17 | $table->string('role')->nullable(); 18 | $table->timestamps(); 19 | 20 | $table->unique(['team_id', 'user_id']); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('team_user'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /database/migrations/2020_05_21_300000_create_team_invitations_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->foreignId('team_id')->constrained()->cascadeOnDelete(); 16 | $table->string('email'); 17 | $table->string('role')->nullable(); 18 | $table->string('token', 64)->unique(); 19 | $table->timestamps(); 20 | 21 | $table->unique(['team_id', 'email']); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('team_invitations'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2023_05_25_000000_create_affiliates_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); 14 | $table->string('code')->unique(); 15 | $table->decimal('commission_rate', 5, 2); 16 | $table->enum('status', ['active', 'inactive'])->default('active'); 17 | $table->json('custom_rates')->nullable(); 18 | $table->decimal('total_earnings', 10, 2)->default(0); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | public function down() 24 | { 25 | Schema::dropIfExists('affiliates'); 26 | } 27 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_01_create_file_shares_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->integer('file_id')->nullable(); 14 | $table->integer('user_id')->nullable(); 15 | $table->string('permission')->nullable()->comment('read, write, delete'); 16 | $table->timestamps(); 17 | }); 18 | } 19 | 20 | public function down() 21 | { 22 | Schema::dropIfExists('file_shares'); 23 | } 24 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_01_create_files_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('name'); 14 | $table->string('path'); 15 | $table->string('mime_type'); 16 | $table->integer('size'); 17 | $table->integer('folder_id')->nullable(); 18 | $table->integer('user_id')->nullable(); 19 | $table->integer('team_id')->nullable(); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | public function down() 25 | { 26 | Schema::dropIfExists('files'); 27 | } 28 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_01_create_folders_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('name'); 14 | $table->foreignId('parent_id')->nullable()->constrained('folders')->onDelete('cascade'); 15 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); 16 | $table->foreignId('team_id')->nullable()->constrained()->onDelete('cascade'); 17 | $table->timestamps(); 18 | }); 19 | } 20 | 21 | public function down() 22 | { 23 | Schema::dropIfExists('folders'); 24 | } 25 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_10_000000_create_audit_logs_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete(); 14 | $table->string('event'); 15 | $table->string('auditable_type')->nullable(); 16 | $table->unsignedBigInteger('auditable_id')->nullable(); 17 | $table->json('old_values')->nullable(); 18 | $table->json('new_values')->nullable(); 19 | $table->string('ip_address')->nullable(); 20 | $table->string('user_agent')->nullable(); 21 | $table->timestamps(); 22 | 23 | $table->index(['auditable_type', 'auditable_id']); 24 | }); 25 | } 26 | 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('audit_logs'); 30 | } 31 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_10_000000_create_client_notes_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->integer('client_id')->nullable(); 14 | $table->integer('user_id')->nullable(); 15 | $table->text('content'); 16 | $table->timestamps(); 17 | }); 18 | } 19 | 20 | public function down() 21 | { 22 | Schema::dropIfExists('client_notes'); 23 | } 24 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_10_000000_create_discounts_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('code')->unique(); 14 | $table->string('name'); 15 | $table->text('description')->nullable(); 16 | $table->enum('type', ['percentage', 'fixed']); 17 | $table->decimal('value', 10, 2); 18 | $table->string('currency')->nullable(); 19 | $table->timestamp('start_date')->nullable(); 20 | $table->timestamp('end_date')->nullable(); 21 | $table->integer('max_uses')->nullable(); 22 | $table->integer('used_count')->default(0); 23 | $table->boolean('is_active')->default(true); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | public function down() 29 | { 30 | Schema::dropIfExists('discounts'); 31 | } 32 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_10_000000_create_integrations_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('user_id')->constrained()->cascadeOnDelete(); 14 | $table->string('provider'); 15 | $table->text('token'); 16 | $table->text('refresh_token')->nullable(); 17 | $table->timestamp('expires_at')->nullable(); 18 | $table->json('scopes')->nullable(); 19 | $table->json('settings')->nullable(); 20 | $table->timestamps(); 21 | 22 | $table->unique(['user_id', 'provider']); 23 | }); 24 | } 25 | 26 | public function down(): void 27 | { 28 | Schema::dropIfExists('integrations'); 29 | } 30 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_10_000000_create_tax_rates_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('team_id')->constrained(); 14 | $table->string('name'); 15 | $table->string('country'); 16 | $table->string('state')->nullable(); 17 | $table->decimal('rate', 5, 2); 18 | $table->string('service_type')->nullable(); 19 | $table->boolean('is_active')->default(true); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | public function down() 25 | { 26 | Schema::dropIfExists('tax_rates'); 27 | } 28 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_10_000001_create_late_fee_configurations_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('team_id')->constrained()->onDelete('cascade'); 14 | $table->enum('fee_type', ['percentage', 'fixed']); 15 | $table->decimal('fee_amount', 10, 2); 16 | $table->integer('grace_period_days')->default(0); 17 | $table->decimal('max_fee_amount', 10, 2)->nullable(); 18 | $table->boolean('is_compound')->default(false); 19 | $table->enum('frequency', ['one-time', 'daily', 'weekly', 'monthly'])->default('one-time'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | public function down() 25 | { 26 | Schema::dropIfExists('late_fee_configurations'); 27 | } 28 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_10_000001_create_recurring_billing_configurations_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->integer('invoice_id')->nullable(); 14 | $table->string('frequency'); // monthly, quarterly, yearly 15 | $table->integer('billing_day')->nullable(); 16 | $table->date('next_billing_date'); 17 | $table->boolean('is_active')->default(true); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | public function down() 23 | { 24 | Schema::dropIfExists('recurring_billing_configurations'); 25 | } 26 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_10_000001_create_tickets_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); 14 | $table->string('title'); 15 | $table->text('description'); 16 | $table->enum('status', ['open', 'in_progress', 'closed'])->default('open'); 17 | $table->enum('priority', ['low', 'medium', 'high'])->default('medium'); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | public function down() 23 | { 24 | Schema::dropIfExists('tickets'); 25 | } 26 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_10_000002_create_ticket_responses_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('ticket_id')->constrained()->onDelete('cascade'); 14 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); 15 | $table->text('message'); 16 | $table->timestamps(); 17 | }); 18 | } 19 | 20 | public function down() 21 | { 22 | Schema::dropIfExists('ticket_responses'); 23 | } 24 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_20_000000_add_dashboard_preferences_to_users_table.php: -------------------------------------------------------------------------------- 1 | json('dashboard_preferences')->nullable(); 13 | }); 14 | } 15 | 16 | public function down() 17 | { 18 | Schema::table('users', function (Blueprint $table) { 19 | $table->dropColumn('dashboard_preferences'); 20 | }); 21 | } 22 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_20_000000_create_audit_logs_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->integer('team_id')->nullable(); 14 | $table->integer('user_id')->nullable(); 15 | $table->string('action'); 16 | $table->string('entity_type')->nullable(); 17 | $table->integer('entity_id')->nullable(); 18 | $table->json('old_values')->nullable(); 19 | $table->json('new_values')->nullable(); 20 | $table->string('ip_address')->nullable(); 21 | $table->string('user_agent')->nullable(); 22 | $table->timestamps(); 23 | 24 | $table->index(['entity_type', 'entity_id']); 25 | }); 26 | } 27 | 28 | public function down() 29 | { 30 | Schema::dropIfExists('audit_logs'); 31 | } 32 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_20_000000_create_clients_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('name'); 14 | $table->string('email')->unique(); 15 | $table->string('phone')->nullable(); 16 | $table->string('company')->nullable(); 17 | $table->text('address')->nullable(); 18 | $table->text('notes')->nullable(); 19 | $table->enum('status', ['active', 'inactive'])->default('active'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('clients'); 27 | } 28 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_20_000000_create_reports_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('team_id')->constrained(); 14 | $table->string('name'); 15 | $table->string('type'); 16 | $table->datetime('start_date')->nullable(); 17 | $table->datetime('end_date')->nullable(); 18 | $table->json('filters')->nullable(); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | public function down() 24 | { 25 | Schema::dropIfExists('reports'); 26 | } 27 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_20_000001_create_email_templates_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('name'); 14 | $table->string('type'); // invoice_generated, overdue_reminder, etc. 15 | $table->string('subject'); 16 | $table->text('body'); 17 | $table->foreignId('team_id')->nullable()->constrained()->cascadeOnDelete(); 18 | $table->boolean('is_default')->default(false); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | public function down() 24 | { 25 | Schema::dropIfExists('email_templates'); 26 | } 27 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_20_000001_create_invoice_disputes_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->integer('invoice_id')->nullable(); 14 | $table->integer('customer_id')->constrained(); 15 | $table->string('status')->default('open'); 16 | $table->string('reason'); 17 | $table->text('description'); 18 | $table->text('resolution_notes')->nullable(); 19 | $table->timestamp('resolved_at')->nullable(); 20 | $table->integer('resolved_by')->nullable()->constrained('users'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | public function down() 26 | { 27 | Schema::dropIfExists('invoice_disputes'); 28 | } 29 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_20_000001_create_tax_exemptions_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->integer('customer_id')->nullable(); 14 | $table->string('exemption_number')->nullable(); 15 | $table->text('reason'); 16 | $table->date('expiry_date')->nullable(); 17 | $table->boolean('is_active')->default(true); 18 | $table->string('documentation_path')->nullable(); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | public function down() 24 | { 25 | Schema::dropIfExists('tax_exemptions'); 26 | } 27 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_20_000002_create_hosting_servers_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('name'); 14 | $table->string('hostname'); 15 | $table->enum('control_panel', ['cpanel', 'plesk', 'directadmin', 'virtualmin']); 16 | $table->string('api_token'); 17 | $table->string('api_url'); 18 | $table->boolean('is_active')->default(true); 19 | $table->integer('max_accounts')->default(0); 20 | $table->integer('active_accounts')->default(0); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | public function down() 26 | { 27 | Schema::dropIfExists('hosting_servers'); 28 | } 29 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_20_create_saved_searches_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); 14 | $table->string('name'); 15 | $table->json('criteria'); 16 | $table->string('share_token')->nullable()->unique(); 17 | $table->timestamps(); 18 | }); 19 | } 20 | 21 | public function down() 22 | { 23 | Schema::dropIfExists('saved_searches'); 24 | } 25 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_20_create_subscription_plans_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('name'); 14 | $table->string('code')->unique(); 15 | $table->text('description'); 16 | $table->decimal('price', 10, 2); 17 | $table->string('currency', 3)->default('USD'); 18 | $table->json('features'); 19 | $table->boolean('is_active')->default(true); 20 | $table->integer('trial_days')->default(0); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | public function down() 26 | { 27 | Schema::dropIfExists('subscription_plans'); 28 | } 29 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_21_000001_create_reminder_settings_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('team_id')->constrained()->cascadeOnDelete(); 14 | $table->integer('days_before_reminder')->default(1); 15 | $table->integer('reminder_frequency')->default(7); 16 | $table->integer('max_reminders')->default(3); 17 | $table->boolean('is_active')->default(true); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | public function down() 23 | { 24 | Schema::dropIfExists('reminder_settings'); 25 | } 26 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_25_000000_create_usage_records_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->integer('subscription_id')->nullable(); 14 | $table->string('metric_name'); 15 | $table->decimal('quantity', 10, 2); 16 | $table->timestamp('recorded_at'); 17 | $table->boolean('processed')->default(false); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | public function down() 23 | { 24 | Schema::dropIfExists('usage_records'); 25 | } 26 | }; -------------------------------------------------------------------------------- /database/migrations/2024_01_25_000001_create_payment_plans_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->integer('invoice_id')->nullable(); 14 | $table->integer('total_installments'); 15 | $table->decimal('installment_amount', 10, 2); 16 | $table->string('frequency'); 17 | $table->timestamp('start_date')->nullable(); 18 | $table->timestamp('next_due_date')->nullable(); 19 | $table->string('status')->default('active'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | public function down() 25 | { 26 | Schema::dropIfExists('payment_plans'); 27 | } 28 | }; -------------------------------------------------------------------------------- /database/migrations/2024_02_15_000000_create_invoice_templates_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('name'); 14 | $table->string('company_name'); 15 | $table->text('company_address')->nullable(); 16 | $table->string('company_phone')->nullable(); 17 | $table->string('company_email')->nullable(); 18 | $table->string('logo_path')->nullable(); 19 | $table->text('header_text')->nullable(); 20 | $table->text('footer_text')->nullable(); 21 | $table->string('color_scheme')->default('#000000'); 22 | $table->boolean('is_default')->default(false); 23 | $table->foreignId('team_id')->nullable()->constrained()->cascadeOnDelete(); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | public function down() 29 | { 30 | Schema::dropIfExists('invoice_templates'); 31 | } 32 | }; -------------------------------------------------------------------------------- /database/migrations/2024_02_15_000001_create_payment_histories_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->integer('payment_id')->nullable(); 14 | $table->integer('invoice_id')->nullable(); 15 | $table->integer('customer_id')->nullable(); 16 | $table->decimal('amount', 10, 2); 17 | $table->string('currency'); 18 | $table->string('payment_method'); 19 | $table->string('transaction_id')->nullable(); 20 | $table->string('status'); 21 | $table->text('notes')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | public function down() 27 | { 28 | Schema::dropIfExists('payment_histories'); 29 | } 30 | }; -------------------------------------------------------------------------------- /database/migrations/2024_06_17_143940_create_customers_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email')->unique(); 18 | $table->string('phone_number')->nullable(); 19 | $table->string('address')->nullable(); 20 | $table->string('city')->nullable(); 21 | $table->string('state')->nullable(); 22 | $table->string('postal_code')->nullable(); 23 | $table->string('country')->nullable(); 24 | $table->boolean('sms_notifications_enabled')->default(true); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists('customers'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2024_06_17_145015_create_products_services_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->string('name'); 16 | $table->text('description'); 17 | $table->decimal('price', 8, 2); 18 | $table->enum('type', ['product', 'service']); 19 | $table->integer('product_type_id')->nullable()->constrained(); 20 | $table->integer('hosting_server_id')->nullable()->constrained(); 21 | $table->integer('trial_days')->default(0); 22 | $table->boolean('trial_enabled')->default(false); 23 | $table->timestamps(); 24 | }); 25 | } 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('products_services'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2024_06_17_150814_create_invoice_items_table.php: -------------------------------------------------------------------------------- 1 | id(); 14 | $table->unsignedBigInteger('invoice_id'); 15 | $table->unsignedBigInteger('product_service_id'); 16 | $table->integer('quantity'); 17 | $table->decimal('unit_price', 10, 2); 18 | $table->decimal('total', 12, 2); 19 | $table->string('currency', 3); 20 | $table->timestamps(); 21 | $table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); 22 | $table->foreign('product_service_id')->references('id')->on('products_services'); 23 | }); 24 | } 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('invoice_items'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2024_06_17_151700_create_payments_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('invoice_id')->constrained('invoices')->onDelete('cascade'); 17 | $table->date('payment_date'); 18 | $table->decimal('amount', 10, 2); 19 | $table->string('currency', 3); 20 | $table->enum('payment_method', ['credit card', 'bank transfer', 'PayPal']); 21 | $table->string('transaction_id')->unique(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('payments'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2024_06_18_000000_create_credits_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('customer_id')->constrained()->onDelete('cascade'); 14 | $table->decimal('amount', 10, 2); 15 | $table->string('description')->nullable(); 16 | $table->date('expiry_date')->nullable(); 17 | $table->timestamps(); 18 | }); 19 | } 20 | 21 | public function down() 22 | { 23 | Schema::dropIfExists('credits'); 24 | } 25 | } -------------------------------------------------------------------------------- /database/migrations/2024_06_18_000000_create_currencies_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('code', 3)->unique(); 14 | $table->string('name'); 15 | $table->decimal('exchange_rate', 10, 6); 16 | $table->timestamps(); 17 | }); 18 | } 19 | 20 | public function down(): void 21 | { 22 | Schema::dropIfExists('currencies'); 23 | } 24 | }; -------------------------------------------------------------------------------- /database/migrations/2024_06_18_000000_create_hosting_accounts_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('customer_id')->constrained()->onDelete('cascade'); 14 | $table->foreignId('subscription_id')->constrained()->onDelete('cascade'); 15 | $table->string('control_panel'); 16 | $table->string('username'); 17 | $table->string('domain'); 18 | $table->string('package'); 19 | $table->string('status'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | public function down() 25 | { 26 | Schema::dropIfExists('hosting_accounts'); 27 | } 28 | }; -------------------------------------------------------------------------------- /database/migrations/2024_06_18_000001_add_refund_status_to_payments_table.php: -------------------------------------------------------------------------------- 1 | enum('refund_status', ['none', 'pending', 'completed'])->default('none'); 13 | }); 14 | } 15 | 16 | public function down() 17 | { 18 | Schema::table('payments', function (Blueprint $table) { 19 | $table->dropColumn('refund_status'); 20 | }); 21 | } 22 | } -------------------------------------------------------------------------------- /database/migrations/2024_07_24_080000_create_menus_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('name'); 14 | $table->string('url'); 15 | $table->unsignedBigInteger('parent_id')->nullable(); 16 | $table->integer('order')->default(0); 17 | $table->timestamps(); 18 | $table->softDeletes(); 19 | 20 | $table->foreign('parent_id')->references('id')->on('menus')->onDelete('cascade'); 21 | }); 22 | } 23 | 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('menus'); 27 | } 28 | }; -------------------------------------------------------------------------------- /database/migrations/2024_07_29_create_payment_gateways_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('name'); 14 | $table->string('api_key'); 15 | $table->string('secret_key'); 16 | $table->boolean('is_active')->default(true); 17 | $table->timestamps(); 18 | }); 19 | 20 | Schema::table('payments', function (Blueprint $table) { 21 | $table->foreignId('payment_gateway_id')->after('invoice_id')->constrained(); 22 | }); 23 | } 24 | 25 | public function down() 26 | { 27 | Schema::table('payments', function (Blueprint $table) { 28 | $table->dropForeign(['payment_gateway_id']); 29 | $table->dropColumn('payment_gateway_id'); 30 | }); 31 | 32 | Schema::dropIfExists('payment_gateways'); 33 | } 34 | }; -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | withPersonalTeam()->create(); 17 | 18 | $this->call([ 19 | SiteSettingsSeeder::class, 20 | PermissionsTableSeeder::class, 21 | RolesSeeder::class, 22 | DefaultTeamSeeder::class, 23 | UserSeeder::class, 24 | MenuSeeder::class, 25 | ]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /database/seeders/DefaultTeamSeeder.php: -------------------------------------------------------------------------------- 1 | 1, 18 | 'name' => 'default', 19 | 'personal_team' => false, 20 | 'user_id' => 1, 21 | ]); 22 | 23 | // Branch::create([ 24 | // 'name' => 'Default Branch', 25 | // 'address' => '123 Main St, City, Country', 26 | // 'phone_number' => '+1234567890', 27 | // 'team_id' => $team->id, 28 | // ]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/seeders/EmailTemplateSeeder.php: -------------------------------------------------------------------------------- 1 | 'Default Invoice Generated', 14 | 'type' => 'invoice_generated', 15 | 'subject' => 'Invoice #{{invoice_number}} Generated', 16 | 'body' => 'Dear {{customer_name}}, 17 | 18 | Your invoice #{{invoice_number}} has been generated for the amount of {{amount}}. 19 | Due date: {{due_date}} 20 | 21 | Thank you for your business!', 22 | 'is_default' => true, 23 | ]); 24 | 25 | EmailTemplate::create([ 26 | 'name' => 'Default Overdue Reminder', 27 | 'type' => 'overdue_reminder', 28 | 'subject' => 'Overdue Invoice Reminder #{{invoice_number}}', 29 | 'body' => 'Dear {{customer_name}}, 30 | 31 | This is a reminder that invoice #{{invoice_number}} for {{amount}} is overdue. 32 | Please process the payment as soon as possible. 33 | 34 | Thank you for your attention to this matter.', 35 | 'is_default' => true, 36 | ]); 37 | } 38 | } -------------------------------------------------------------------------------- /database/seeders/PermissionsSeeder.php: -------------------------------------------------------------------------------- 1 | config('app.name', 'Liberu '), 15 | 'currency' => '£', 16 | 'default_language' => 'en', 17 | 'address' => '123 St, London, UK', 18 | 'country' => 'United Kingdom', 19 | 'email' => 'info@liberurealestate.com', 20 | 'phone_01' => '+44 123 456 7890', 21 | 'phone_02' => '+44 123 456 7890', 22 | 'phone_03' => '+44 123 456 7890', 23 | 'phone_04' => '+44 123 456 7890', 24 | 'facebook' => 'https://facebook.com/liberusoftware', 25 | 'twitter' => 'https://twitter.com/liberusoftware', 26 | 'github' => 'https://Github.com/liberusoftware', 27 | 'youtube' => 'https://YouTube.com/@liberusoftware', 28 | ]; 29 | 30 | foreach ($data as $key => $value) { 31 | SiteConfig::store($key, $value); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /database/seeders/TeamSeeder.php: -------------------------------------------------------------------------------- 1 | 1, 18 | 'name' => 'default', 19 | 'personal_team' => false, 20 | 'user_id' => 1, 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /database/seeders/UserSeeder.php: -------------------------------------------------------------------------------- 1 | id); 20 | 21 | $adminUser = User::create([ 22 | 'name' => 'Admin User', 23 | 'email' => 'admin@example.com', 24 | 'password' => Hash::make('password'), 25 | 'email_verified_at' => now(), 26 | ]); 27 | $adminUser->assignRole('super_admin'); 28 | 29 | // Create teams for admin and staff users 30 | $this->createTeamForUser($adminUser); 31 | } 32 | 33 | private function createTeamForUser($user) 34 | { 35 | $team = Team::first(); 36 | $team->users()->attach($user); 37 | 38 | $user->current_team_id = 1; 39 | $user->save(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /error_log: -------------------------------------------------------------------------------- 1 | [09-Apr-2025 23:43:51 UTC] PHP Warning: PHP Startup: session.gc_divisor must be greater than 0 in Unknown on line 0 2 | [09-Apr-2025 23:47:30 UTC] PHP Warning: PHP Startup: session.gc_divisor must be greater than 0 in Unknown on line 0 3 | [09-Apr-2025 23:47:31 UTC] PHP Warning: PHP Startup: session.gc_divisor must be greater than 0 in Unknown on line 0 4 | [09-Apr-2025 23:47:31 UTC] PHP Warning: PHP Startup: session.gc_divisor must be greater than 0 in Unknown on line 0 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "devDependencies": { 9 | "@tailwindcss/forms": "^0.5.7", 10 | "@tailwindcss/typography": "^0.5.10", 11 | "autoprefixer": "^10.4.16", 12 | "laravel-vite-plugin": "^1.0.6", 13 | "postcss": "^8.5.1", 14 | "postcss-nesting": "^13.0.1", 15 | "tailwindcss": "^3.4.0", 16 | "vite": "^6.3.5" 17 | }, 18 | "dependencies": { 19 | "preline": "^2.4.1", 20 | "vite-plugin-static-copy": "^3.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | 'tailwindcss/nesting': 'postcss-nesting', 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/build/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liberu-billing/billing-laravel/2a52d2077ef0c6ac0f99bd6e62ab1ca4f8b6ff22/public/build/images/logo.png -------------------------------------------------------------------------------- /public/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources/css/app.css": { 3 | "file": "assets/app-D14Yq6Y_.css", 4 | "src": "resources/css/app.css", 5 | "isEntry": true 6 | }, 7 | "resources/css/filament/admin/theme.css": { 8 | "file": "assets/theme-B6Z1S7Jt.css", 9 | "src": "resources/css/filament/admin/theme.css", 10 | "isEntry": true 11 | }, 12 | "resources/js/app.js": { 13 | "file": "assets/app-10Rq7FPl.js", 14 | "name": "app", 15 | "src": "resources/js/app.js", 16 | "isEntry": true 17 | } 18 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liberu-billing/billing-laravel/2a52d2077ef0c6ac0f99bd6e62ab1ca4f8b6ff22/public/favicon.ico -------------------------------------------------------------------------------- /public/js/filament/forms/components/key-value.js: -------------------------------------------------------------------------------- 1 | function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/tags-input.js: -------------------------------------------------------------------------------- 1 | function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/textarea.js: -------------------------------------------------------------------------------- 1 | function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init:function(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /resources/css/filament/admin/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import preset from '../../../../vendor/filament/filament/tailwind.config.preset' 2 | 3 | export default { 4 | presets: [preset], 5 | content: [ 6 | './app/Filament/**/*.php', 7 | './resources/views/**/*.blade.php', 8 | './resources/views/filament/**/*.blade.php', 9 | './vendor/filament/**/*.blade.php', 10 | './vendor/laravel/jetstream/**/*.blade.php', 11 | ], 12 | } 13 | -------------------------------------------------------------------------------- /resources/css/filament/admin/theme.css: -------------------------------------------------------------------------------- 1 | @import '/vendor/filament/filament/resources/css/theme.css'; 2 | 3 | @config 'tailwind.config.js'; 4 | 5 | /* .fi-body { 6 | background-color: #ebebeb !important; 7 | } 8 | 9 | .fi-sidebar { 10 | background-color: #292828 !important; 11 | } */ -------------------------------------------------------------------------------- /resources/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liberu-billing/billing-laravel/2a52d2077ef0c6ac0f99bd6e62ab1ca4f8b6ff22/resources/images/logo.png -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import 'preline/dist/preline.js'; -------------------------------------------------------------------------------- /resources/markdown/policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | Edit this file to define the privacy policy for your application. 4 | -------------------------------------------------------------------------------- /resources/markdown/terms.md: -------------------------------------------------------------------------------- 1 | # Terms of Service 2 | 3 | Edit this file to define the terms of service for your application. 4 | -------------------------------------------------------------------------------- /resources/views/api/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('API Tokens') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('api.api-token-manager') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/auth/confirm-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {{ __('This is a secure area of the application. Please confirm your password before continuing.') }} 9 |
10 | 11 | 12 | 13 |
14 | @csrf 15 | 16 |
17 | 18 | 19 |
20 | 21 |
22 | 23 | {{ __('Confirm') }} 24 | 25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /resources/views/components/action-link.blade.php: -------------------------------------------------------------------------------- 1 | merge(['class' => 'inline-flex items-center px-4 py-2 bg-white border border-gray-800 rounded-md font-semibold text-xs text-gray-800 uppercase tracking-widest hover:bg-gray-200 hover:border-gray-600 active:border-gray-900 focus:outline-none focus:border-gray-900 focus:shadow-outline-gray disabled:opacity-25 transition ease-in-out duration-150'])}}> 2 | {{ $slot }} 3 | 4 | -------------------------------------------------------------------------------- /resources/views/components/action-message.blade.php: -------------------------------------------------------------------------------- 1 | @props(['on']) 2 | 3 |
merge(['class' => 'text-sm text-gray-600']) }}> 9 | {{ $slot->isEmpty() ? 'Saved.' : $slot }} 10 |
11 | -------------------------------------------------------------------------------- /resources/views/components/action-section.blade.php: -------------------------------------------------------------------------------- 1 |
merge(['class' => 'md:grid md:grid-cols-3 md:gap-6']) }}> 2 | 3 | {{ $title }} 4 | {{ $description }} 5 | 6 | 7 |
8 |
9 | {{ $content }} 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /resources/views/components/application-mark.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/views/components/authentication-card-logo.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/views/components/authentication-card.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ $logo }} 4 |
5 | 6 |
7 | {{ $slot }} 8 |
9 |
10 | -------------------------------------------------------------------------------- /resources/views/components/button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/components/buttons.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
7 | 4 | -------------------------------------------------------------------------------- /resources/views/components/dialog-modal.blade.php: -------------------------------------------------------------------------------- 1 | @props(['id' => null, 'maxWidth' => null]) 2 | 3 | 4 |
5 |
6 | {{ $title }} 7 |
8 | 9 |
10 | {{ $content }} 11 |
12 |
13 | 14 |
15 | {{ $footer }} 16 |
17 |
18 | -------------------------------------------------------------------------------- /resources/views/components/dropdown-link.blade.php: -------------------------------------------------------------------------------- 1 | merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/components/form-section.blade.php: -------------------------------------------------------------------------------- 1 | @props(['submit']) 2 | 3 |
merge(['class' => 'md:grid md:grid-cols-3 md:gap-6']) }}> 4 | 5 | {{ $title }} 6 | {{ $description }} 7 | 8 | 9 |
10 |
11 |
12 |
13 | {{ $form }} 14 |
15 |
16 | 17 | @if (isset($actions)) 18 |
19 | {{ $actions }} 20 |
21 | @endif 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /resources/views/components/home-header.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 25 |
26 | -------------------------------------------------------------------------------- /resources/views/components/input-error.blade.php: -------------------------------------------------------------------------------- 1 | @props(['for']) 2 | 3 | @error($for) 4 |

merge(['class' => 'text-sm text-red-600']) }}>{{ $message }}

5 | @enderror 6 | -------------------------------------------------------------------------------- /resources/views/components/input.blade.php: -------------------------------------------------------------------------------- 1 | @props(['disabled' => false]) 2 | 3 | merge(['class' => 'border-gray-300 focus:border-indigo-500 text-gray-700 focus:ring-indigo-500 rounded-md shadow-sm']) !!}> 4 | -------------------------------------------------------------------------------- /resources/views/components/label.blade.php: -------------------------------------------------------------------------------- 1 | @props(['value']) 2 | 3 | 6 | -------------------------------------------------------------------------------- /resources/views/components/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ config('app.name') }} 11 | 12 | 17 | 18 | @filamentStyles 19 | @vite('resources/css/app.css') 20 | 21 | 22 | 23 | {{ $slot }} 24 | 25 | @filamentScripts 26 | @vite('resources/js/app.js') 27 | 28 | 29 | -------------------------------------------------------------------------------- /resources/views/components/logo.blade.php: -------------------------------------------------------------------------------- 1 | {{ config('app.name') }} 2 | -------------------------------------------------------------------------------- /resources/views/components/nav-link.blade.php: -------------------------------------------------------------------------------- 1 | @props(['active']) 2 | 3 | @php 4 | $classes = ($active ?? false) 5 | ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out' 6 | : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out'; 7 | @endphp 8 | 9 | merge(['class' => $classes]) }}> 10 | {{ $slot }} 11 | 12 | -------------------------------------------------------------------------------- /resources/views/components/products_section.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Our Products

4 |

Discover our range of genealogy tools designed to help you explore your ancestry.

5 |
6 | @foreach($products as $product) 7 |
8 | {{ $product->name }} 9 |
10 |

{{ $product->name }}

11 |

{{ $product->description }}

12 | 13 |
14 |
15 | @endforeach 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /resources/views/components/responsive-nav-link.blade.php: -------------------------------------------------------------------------------- 1 | @props(['active']) 2 | 3 | @php 4 | $classes = ($active ?? false) 5 | ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out' 6 | : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out'; 7 | @endphp 8 | 9 | merge(['class' => $classes]) }}> 10 | {{ $slot }} 11 | 12 | -------------------------------------------------------------------------------- /resources/views/components/secondary-button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/components/section-border.blade.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /resources/views/components/section-title.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ $title }}

4 | 5 |

6 | {{ $description }} 7 |

8 |
9 | 10 |
11 | {{ $aside ?? '' }} 12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/components/socialstream-icons/bitbucket.blade.php: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /resources/views/components/socialstream-icons/facebook.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/components/socialstream-icons/github.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/components/socialstream-icons/gitlab.blade.php: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /resources/views/components/socialstream-icons/google.blade.php: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /resources/views/components/socialstream-icons/linkedin.blade.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/views/components/socialstream-icons/twitter.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/components/switchable-team.blade.php: -------------------------------------------------------------------------------- 1 | @props(['team', 'component' => 'dropdown-link']) 2 | 3 |
4 | @method('PUT') 5 | @csrf 6 | 7 | 8 | 9 | 10 | 11 |
12 | @if (Auth::user()->isCurrentTeam($team)) 13 | 14 | 15 | 16 | @endif 17 | 18 |
{{ $team->name }}
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /resources/views/components/validation-errors.blade.php: -------------------------------------------------------------------------------- 1 | @if ($errors->any()) 2 |
3 |
{{ __('Whoops! Something went wrong.') }}
4 | 5 |
    6 | @foreach ($errors->all() as $error) 7 |
  • {{ $error }}
  • 8 | @endforeach 9 |
10 |
11 | @endif 12 | -------------------------------------------------------------------------------- /resources/views/components/why_us_section.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ $title }}

4 |

{{ $description }}

5 |
6 | @foreach($features as $feature) 7 |
8 | {{ $feature['title'] }} 9 |
10 |

{{ $feature['title'] }}

11 |

{{ $feature['description'] }}

12 |
13 |
14 | @endforeach 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /resources/views/emails/dynamic-template.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {!! $content !!} 10 | 11 | -------------------------------------------------------------------------------- /resources/views/emails/team-invitation.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | {{ __('You have been invited to join the :team team!', ['team' => $invitation->team->name]) }} 3 | 4 | @if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::registration())) 5 | {{ __('If you do not have an account, you may create one by clicking the button below. After creating an account, you may click the invitation acceptance button in this email to accept the team invitation:') }} 6 | 7 | @component('mail::button', ['url' => route('register')]) 8 | {{ __('Create Account') }} 9 | @endcomponent 10 | 11 | {{ __('If you already have an account, you may accept this invitation by clicking the button below:') }} 12 | 13 | @else 14 | {{ __('You may accept this invitation by clicking the button below:') }} 15 | @endif 16 | 17 | 18 | @component('mail::button', ['url' => $acceptUrl]) 19 | {{ __('Accept Invitation') }} 20 | @endcomponent 21 | 22 | {{ __('If you did not expect to receive an invitation to this team, you may discard this email.') }} 23 | @endcomponent 24 | -------------------------------------------------------------------------------- /resources/views/filament/filament/pages/api-tokens.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | @livewire(\Laravel\Jetstream\Http\Livewire\ApiTokenManager::class) 4 | 5 | 6 | -------------------------------------------------------------------------------- /resources/views/filament/filament/pages/create-team.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | @livewire(\Laravel\Jetstream\Http\Livewire\CreateTeamForm::class) 4 |
5 | 6 |
7 | -------------------------------------------------------------------------------- /resources/views/filament/filament/pages/edit-team.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @livewire(Laravel\Jetstream\Http\Livewire\UpdateTeamNameForm::class, compact('team')) 3 | 4 | @livewire(Laravel\Jetstream\Http\Livewire\TeamMemberManager::class, compact('team')) 5 | 6 | @if (Gate::check('delete', $team) && ! $team->personal_team) 7 | 8 | 9 | @livewire(Laravel\Jetstream\Http\Livewire\DeleteTeamForm::class, compact('team')) 10 | @endif 11 | 12 | -------------------------------------------------------------------------------- /resources/views/filament/pages/api-tokens.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @livewire(Laravel\Jetstream\Http\Livewire\ApiTokenManager::class) 3 | 4 | -------------------------------------------------------------------------------- /resources/views/filament/pages/create-team.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /resources/views/filament/pages/edit-team.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @livewire(Laravel\Jetstream\Http\Livewire\UpdateTeamNameForm::class, compact('team')) 3 | 4 | @livewire(Laravel\Jetstream\Http\Livewire\TeamMemberManager::class, compact('team')) 5 | 6 | @if (Gate::check('delete', $team) && ! $team->personal_team) 7 | 8 | 9 | @livewire(Laravel\Jetstream\Http\Livewire\DeleteTeamForm::class, compact('team')) 10 | @endif 11 | 12 | -------------------------------------------------------------------------------- /resources/views/layouts/guest.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ config('app.name', 'Laravel') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | @vite(['resources/css/app.css', 'resources/js/app.js']) 16 | 17 | 18 | @livewireStyles 19 | 20 | 21 |
22 | {{ $slot }} 23 |
24 | 25 | @livewireScripts 26 | 27 | 28 | -------------------------------------------------------------------------------- /resources/views/policy.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 | {!! $policy !!} 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/teams/create.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Create Team') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('teams.create-team-form') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/teams/show.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Team Settings') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('teams.update-team-name-form', ['team' => $team]) 11 | 12 | @livewire('teams.team-member-manager', ['team' => $team]) 13 | 14 | @if (Gate::check('delete', $team) && ! $team->personal_team) 15 | 16 | 17 |
18 | @livewire('teams.delete-team-form', ['team' => $team]) 19 |
20 | @endif 21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /resources/views/terms.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 | {!! $terms !!} 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /routes/socialstream.php: -------------------------------------------------------------------------------- 1 | config('socialstream.middleware', ['web'])], function () { 7 | Route::get('/oauth/{provider}', [OAuthController::class, 'redirect'])->name('oauth.redirect'); 8 | Route::match(['get', 'post'], '/oauth/{provider}/callback', [OAuthController::class, 'callback'])->name('oauth.callback'); 9 | }); 10 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | import preset from './vendor/filament/filament/tailwind.config.preset'; 2 | 3 | export default { 4 | presets: [preset], 5 | content: [ 6 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', 7 | './vendor/laravel/jetstream/**/*.blade.php', 8 | './storage/framework/views/*.php', 9 | './resources/views/**/*.blade.php', 10 | './app/Filament/**/*.php', 11 | './resources/views/filament/**/*.blade.php', 12 | './vendor/filament/**/*.blade.php', 13 | 'node_modules/preline/dist/*.js', 14 | ], 15 | plugins: [ 16 | require('preline/plugin'), 17 | ], 18 | }; -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 18 | 19 | return $app; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Feature/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | get('/login'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | 20 | public function test_users_can_authenticate_using_the_login_screen(): void 21 | { 22 | $user = User::factory()->create(); 23 | 24 | $response = $this->post('/login', [ 25 | 'email' => $user->email, 26 | 'password' => 'password', 27 | ]); 28 | 29 | $this->assertAuthenticated(); 30 | $response->assertRedirect(route('dashboard', absolute: false)); 31 | } 32 | 33 | public function test_users_can_not_authenticate_with_invalid_password(): void 34 | { 35 | $user = User::factory()->create(); 36 | 37 | $this->post('/login', [ 38 | 'email' => $user->email, 39 | 'password' => 'wrong-password', 40 | ]); 41 | 42 | $this->assertGuest(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Feature/BrowserSessionsTest.php: -------------------------------------------------------------------------------- 1 | actingAs(User::factory()->create()); 18 | 19 | Livewire::test(LogoutOtherBrowserSessionsForm::class) 20 | ->set('password', 'password') 21 | ->call('logoutOtherBrowserSessions') 22 | ->assertSuccessful(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Feature/CreateTeamTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->withPersonalTeam()->create()); 18 | 19 | Livewire::test(CreateTeamForm::class) 20 | ->set(['state' => ['name' => 'Test Team']]) 21 | ->call('createTeam'); 22 | 23 | $this->assertCount(2, $user->fresh()->ownedTeams); 24 | $this->assertEquals('Test Team', $user->fresh()->ownedTeams()->latest('id')->first()->name); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Feature/DeleteApiTokenTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 21 | } 22 | 23 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 24 | 25 | $token = $user->tokens()->create([ 26 | 'name' => 'Test Token', 27 | 'token' => Str::random(40), 28 | 'abilities' => ['create', 'read'], 29 | ]); 30 | 31 | Livewire::test(ApiTokenManager::class) 32 | ->set(['apiTokenIdBeingDeleted' => $token->id]) 33 | ->call('deleteApiToken'); 34 | 35 | $this->assertCount(0, $user->fresh()->tokens); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | $response->assertStatus(200); 17 | } 18 | 19 | /** 20 | * Test the "/app" route returns a successful response. 21 | */ 22 | public function test_the_app_route_returns_a_successful_response(): void 23 | { 24 | $response = $this->get('/app'); 25 | $response->assertStatus(200); 26 | } 27 | 28 | /** 29 | * Test the "/admin" route returns a successful response. 30 | */ 31 | public function test_the_admin_route_returns_a_successful_response(): void 32 | { 33 | $response = $this->get('/admin'); 34 | $response->assertStatus(200); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Feature/UpdateTeamNameTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->withPersonalTeam()->create()); 18 | 19 | Livewire::test(UpdateTeamNameForm::class, ['team' => $user->currentTeam]) 20 | ->set(['state' => ['name' => 'Test Team']]) 21 | ->call('updateTeamName'); 22 | 23 | $this->assertCount(1, $user->fresh()->ownedTeams); 24 | $this->assertEquals('Test Team', $user->currentTeam->fresh()->name); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import laravel, { refreshPaths } from 'laravel-vite-plugin' 3 | import { viteStaticCopy } from "vite-plugin-static-copy"; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | laravel({ 8 | input: ['resources/css/app.css', 'resources/js/app.js', 9 | 'resources/css/filament/admin/theme.css'], 10 | refresh: [ 11 | ...refreshPaths, 12 | 'app/Filament/**', 13 | 'app/Forms/Components/**', 14 | 'app/Livewire/**', 15 | 'app/Infolists/Components/**', 16 | 'app/Providers/Filament/**', 17 | 'app/Tables/Columns/**', 18 | ], 19 | }), 20 | viteStaticCopy({ 21 | targets: [ 22 | { 23 | src: "resources/images/*", 24 | dest: "images", 25 | }, 26 | ], 27 | }), 28 | ], 29 | }) 30 | --------------------------------------------------------------------------------