├── .styleci.yml ├── LICENSE ├── README.md ├── UPGRADE.md ├── composer.json ├── database ├── factories │ ├── CompanyFactory.php │ └── UserFactory.php ├── migrations │ ├── 0001_01_01_000000_create_users_table.php │ ├── 2020_05_21_100000_create_companies_table.php │ ├── 2020_05_21_200000_create_company_user_table.php │ ├── 2020_05_21_300000_create_company_invitations_table.php │ └── 2020_12_22_000000_create_connected_accounts_table.php └── seeders │ └── DatabaseSeeder.php ├── lang ├── ar │ └── default.php ├── cs │ └── default.php ├── de │ └── default.php ├── en │ └── default.php ├── es │ └── default.php ├── fr │ └── default.php ├── id │ └── default.php ├── ja │ └── default.php ├── ko │ └── default.php ├── nl │ └── default.php ├── pt_BR │ └── default.php └── pt_PT │ └── default.php ├── pint.json ├── resources └── views │ ├── auth │ ├── login.blade.php │ ├── policy.blade.php │ ├── register.blade.php │ └── terms.blade.php │ ├── companies │ ├── company-employee-manager.blade.php │ ├── delete-company-form.blade.php │ └── update-company-name-form.blade.php │ ├── components │ ├── connected-account.blade.php │ ├── grid-section.blade.php │ ├── input-error.blade.php │ ├── input.blade.php │ ├── section-border.blade.php │ ├── socialite-icons │ │ ├── bitbucket.blade.php │ │ ├── facebook.blade.php │ │ ├── github.blade.php │ │ ├── gitlab.blade.php │ │ ├── google.blade.php │ │ ├── linkedin.blade.php │ │ ├── slack.blade.php │ │ └── twitter.blade.php │ └── socialite.blade.php │ ├── filament │ └── pages │ │ ├── companies │ │ ├── company_settings.blade.php │ │ └── create_company.blade.php │ │ └── user │ │ ├── personal-access-tokens.blade.php │ │ └── profile.blade.php │ ├── mail │ └── company-invitation.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 │ ├── update-password-form.blade.php │ └── update-profile-information-form.blade.php ├── src ├── Actions │ ├── GenerateRedirectForProvider.php │ ├── UpdateCompanyEmployeeRole.php │ └── ValidateCompanyDeletion.php ├── Company.php ├── Concerns │ ├── Base │ │ ├── HasAddedProfileComponents.php │ │ ├── HasAutoAcceptInvitations.php │ │ ├── HasBaseActionBindings.php │ │ ├── HasBaseModels.php │ │ ├── HasBaseProfileComponents.php │ │ ├── HasBaseProfileFeatures.php │ │ ├── HasCompanyFeatures.php │ │ ├── HasModals.php │ │ ├── HasNotifications.php │ │ ├── HasPanels.php │ │ ├── HasPermissions.php │ │ ├── HasRoutes.php │ │ └── HasTermsAndPrivacyPolicy.php │ ├── ManagesProfileComponents.php │ └── Socialite │ │ ├── CanEnableSocialite.php │ │ ├── HasConnectedAccountModel.php │ │ ├── HasProviderFeatures.php │ │ ├── HasProviders.php │ │ ├── HasSocialiteActionBindings.php │ │ ├── HasSocialiteComponents.php │ │ └── HasSocialiteProfileFeatures.php ├── ConnectedAccount.php ├── Console │ └── InstallCommand.php ├── Contracts │ ├── AddsCompanyEmployees.php │ ├── CreatesCompanies.php │ ├── CreatesConnectedAccounts.php │ ├── CreatesNewUsers.php │ ├── CreatesUserFromProvider.php │ ├── Credentials.php │ ├── DeletesCompanies.php │ ├── DeletesUsers.php │ ├── GeneratesProviderRedirect.php │ ├── HandlesInvalidState.php │ ├── InvitesCompanyEmployees.php │ ├── RemovesCompanyEmployees.php │ ├── ResolvesSocialiteUsers.php │ ├── SetsUserPasswords.php │ ├── UpdatesCompanyNames.php │ ├── UpdatesConnectedAccounts.php │ ├── UpdatesUserPasswords.php │ └── UpdatesUserProfileInformation.php ├── Credentials.php ├── Employeeship.php ├── Enums │ ├── Feature.php │ └── Provider.php ├── Events │ ├── AddingCompany.php │ ├── AddingCompanyEmployee.php │ ├── CompanyCreated.php │ ├── CompanyDeleted.php │ ├── CompanyEmployeeAdded.php │ ├── CompanyEmployeeRemoved.php │ ├── CompanyEmployeeUpdated.php │ ├── CompanyEvent.php │ ├── CompanyUpdated.php │ ├── ConnectedAccountCreated.php │ ├── ConnectedAccountDeleted.php │ ├── ConnectedAccountEvent.php │ ├── ConnectedAccountUpdated.php │ ├── InvitingCompanyEmployee.php │ └── RemovingCompanyEmployee.php ├── FilamentCompanies.php ├── FilamentCompaniesServiceProvider.php ├── HasCompanies.php ├── HasConnectedAccounts.php ├── HasProfilePhoto.php ├── Http │ ├── Controllers │ │ ├── CompanyInvitationController.php │ │ └── OAuthController.php │ ├── Livewire │ │ ├── CompanyEmployeeManager.php │ │ ├── ConnectedAccountsForm.php │ │ ├── CreateCompanyForm.php │ │ ├── DeleteCompanyForm.php │ │ ├── DeleteUserForm.php │ │ ├── LogoutOtherBrowserSessionsForm.php │ │ ├── SetPasswordForm.php │ │ ├── UpdateCompanyNameForm.php │ │ ├── UpdatePasswordForm.php │ │ └── UpdateProfileInformationForm.php │ └── Responses │ │ └── Auth │ │ └── FilamentCompaniesRegistrationResponse.php ├── Listeners │ └── SwitchCurrentCompany.php ├── Mail │ └── CompanyInvitation.php ├── OwnerRole.php ├── Pages │ ├── Auth │ │ ├── Login.php │ │ ├── PrivacyPolicy.php │ │ ├── Register.php │ │ └── Terms.php │ ├── Company │ │ ├── CompanySettings.php │ │ └── CreateCompany.php │ └── User │ │ ├── PersonalAccessTokens.php │ │ └── Profile.php ├── RedirectsActions.php ├── Role.php ├── Rules │ └── Role.php └── SetsProfilePhotoFromUrl.php └── stubs ├── app ├── Actions │ └── FilamentCompanies │ │ ├── AddCompanyEmployee.php │ │ ├── CreateCompany.php │ │ ├── CreateConnectedAccount.php │ │ ├── CreateNewUser.php │ │ ├── CreateUserFromProvider.php │ │ ├── DeleteCompany.php │ │ ├── DeleteUser.php │ │ ├── DeleteUserWithSocialite.php │ │ ├── HandleInvalidState.php │ │ ├── InviteCompanyEmployee.php │ │ ├── RemoveCompanyEmployee.php │ │ ├── ResolveSocialiteUser.php │ │ ├── SetUserPassword.php │ │ ├── UpdateCompanyName.php │ │ ├── UpdateConnectedAccount.php │ │ ├── UpdateUserPassword.php │ │ └── UpdateUserProfileInformation.php ├── Models │ ├── Company.php │ ├── CompanyInvitation.php │ ├── ConnectedAccount.php │ ├── Employeeship.php │ ├── User.php │ └── UserWithSocialite.php ├── Policies │ ├── CompanyPolicy.php │ └── ConnectedAccountPolicy.php └── Providers │ ├── FilamentCompaniesServiceProvider.php │ └── FilamentCompaniesWithSocialiteServiceProvider.php └── resources └── markdown ├── policy.md └── terms.md /.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | disabled: 4 | - no_unused_imports 5 | js: 6 | finder: 7 | not-name: 8 | - vite.config.js 9 | css: true 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 andrewdwallo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | 3 | ## Upgrading from FilamentCompanies 3.x to 4.x 4 | 5 | This major release introduces significant changes designed to streamline the usage of FilamentCompanies. Here’s how to migrate your project from 3.x to 4.x. 6 | 7 | ### Breaking Changes 8 | 9 | 1. Removal of Classes: 10 | - `Wallo\FilamentCompanies\Features` 11 | - `Wallo\FilamentCompanies\Providers` 12 | - `Wallo\FilamentCompanies\Socialite` 13 | 14 | 2. Introduction of Enums: 15 | - Functionality previously available through the `Providers` class has been replaced by the `Wallo\FilamentCompanies\Enums\Provider` enum. 16 | - Some functionality previously available through the `Socialite` class has been replaced by the `Wallo\FilamentCompanies\Enums\Feature` enum. 17 | 18 | 3. Removal of the `MakeUserCommand` command. 19 | - It isn't necessary and was removed to simplify the package. 20 | 21 | ### Migration Steps 22 | 23 | #### Dependency Versions 24 | 25 | You should first make sure you follow the [Laravel 11 Upgrade Guide](https://laravel.com/docs/11.x/upgrade) and then upgrade your `andrewdwallo/filament-companies` dependency to `^4.0` within your application's `composer.json` file. Then, run the `composer update` command: 26 | 27 | composer update 28 | 29 | #### Socialite Providers 30 | 31 | For Socialite providers, replace the usage of the `Wallo\FilamentCompanies\Providers` class with the `Wallo\FilamentCompanies\Enums\Provider` enum. 32 | 33 | Before: 34 | 35 | ```php 36 | 37 | use Wallo\FilamentCompanies\Providers; 38 | 39 | // Enable the following Socialite providers. 40 | Providers::github(), 41 | // And so on for the other providers... 42 | 43 | // Determine if the following Socialite providers are enabled. 44 | Providers::hasGithub(), 45 | // And so on for the other providers... 46 | ``` 47 | 48 | After: 49 | 50 | ```php 51 | 52 | use Wallo\FilamentCompanies\Enums\Provider; 53 | 54 | // Enable the following Socialite providers. 55 | Provider::Github, 56 | // And so on for the other providers... 57 | 58 | // Determine if the following Socialite providers are enabled. 59 | Provider::Github->isEnabled(), 60 | // And so on for the other providers... 61 | 62 | ``` 63 | 64 | #### Socialite Features 65 | 66 | For Socialite features, replace the usage of the `Wallo\FilamentCompanies\Socialite` class with the `Wallo\FilamentCompanies\Enums\Feature` enum. 67 | 68 | Before: 69 | 70 | ```php 71 | 72 | use Wallo\FilamentCompanies\Socialite; 73 | 74 | // Enable the following features. 75 | Socialite::rememberSession(), 76 | // And so on for the other features... 77 | 78 | // Determine if the following features are enabled. 79 | Socialite::hasRememberSessionFeature(), 80 | // And so on for the other features... 81 | 82 | ``` 83 | 84 | After: 85 | 86 | ```php 87 | 88 | use Wallo\FilamentCompanies\Enums\Feature; 89 | 90 | // Enable the following features. 91 | Feature::RememberSession, 92 | // And so on for the other features... 93 | 94 | // Determine if the following features are enabled. 95 | Feature::RememberSession->isEnabled(), 96 | // And so on for the other features... 97 | ``` 98 | 99 | ### Important Notes 100 | > The rest of the methods previously available in the `Socialite` and `Features` classes are still available and were moved to the main `Wallo\FilamentCompanies\FilamentCompanies` class. 101 | 102 | ### Further Assistance 103 | Should you encounter any issues during the upgrade process, please don’t hesitate to reach out Discord or by creating a new Discussion on GitHub. 104 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "andrewdwallo/filament-companies", 3 | "description": "A comprehensive Laravel authentication and authorization system designed for Filament, focusing on multi-tenant company management.", 4 | "keywords": [ 5 | "andrewdwallo", 6 | "filament-companies", 7 | "filament companies", 8 | "laravel", 9 | "socialite", 10 | "authentication", 11 | "filament", 12 | "multi-tenant", 13 | "company management", 14 | "erp", 15 | "saas", 16 | "crm", 17 | "business management" 18 | ], 19 | "homepage": "https://github.com/andrewdwallo/filament-companies", 20 | "license": "MIT", 21 | "authors": [ 22 | { 23 | "name": "Andrew Wallo", 24 | "email": "andrewdwallo@gmail.com", 25 | "role": "Developer" 26 | } 27 | ], 28 | "require": { 29 | "php": "^8.2", 30 | "ext-json": "*", 31 | "filament/filament": "^3.2.29", 32 | "illuminate/console": "^11.0|^12.0", 33 | "illuminate/contracts": "^11.0|^12.0", 34 | "illuminate/support": "^11.0|^12.0", 35 | "laravel/socialite": "^5.12", 36 | "matomo/device-detector": "^6.1" 37 | }, 38 | "require-dev": { 39 | "laravel/pint": "^1.14", 40 | "laravel/sanctum": "^4.0", 41 | "livewire/livewire": "^3.4.9", 42 | "mockery/mockery": "^1.6", 43 | "orchestra/testbench": "^9.0|^10.0", 44 | "phpunit/phpunit": "^10.5|^11.5.3" 45 | }, 46 | "autoload": { 47 | "psr-4": { 48 | "Wallo\\FilamentCompanies\\": "src/" 49 | } 50 | }, 51 | "autoload-dev": { 52 | "psr-4": { 53 | "App\\": "stubs/app/", 54 | "Database\\Factories\\": "database/factories/" 55 | } 56 | }, 57 | "extra": { 58 | "laravel": { 59 | "providers": [ 60 | "Wallo\\FilamentCompanies\\FilamentCompaniesServiceProvider" 61 | ] 62 | } 63 | }, 64 | "config": { 65 | "sort-packages": true 66 | }, 67 | "minimum-stability": "dev", 68 | "prefer-stable": true 69 | } 70 | -------------------------------------------------------------------------------- /database/factories/CompanyFactory.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public function definition(): array 24 | { 25 | return [ 26 | 'name' => $this->faker->unique()->company(), 27 | 'user_id' => User::factory(), 28 | 'personal_company' => true, 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function definition(): array 32 | { 33 | return [ 34 | 'name' => fake()->name(), 35 | 'email' => fake()->unique()->safeEmail(), 36 | 'email_verified_at' => now(), 37 | 'password' => static::$password ??= Hash::make('password'), 38 | 'remember_token' => Str::random(10), 39 | 'profile_photo_path' => null, 40 | 'current_company_id' => null, 41 | ]; 42 | } 43 | 44 | /** 45 | * Indicate that the model's email address should be unverified. 46 | */ 47 | public function unverified(): static 48 | { 49 | return $this->state(fn (array $attributes) => [ 50 | 'email_verified_at' => null, 51 | ]); 52 | } 53 | 54 | /** 55 | * Indicate that the user should have a personal company. 56 | */ 57 | public function withPersonalCompany(?callable $callback = null): static 58 | { 59 | if (! FilamentCompanies::hasCompanyFeatures()) { 60 | return $this->state([]); 61 | } 62 | 63 | return $this->has( 64 | Company::factory() 65 | ->state(fn (array $attributes, User $user) => [ 66 | 'name' => $user->name . '\'s Company', 67 | 'user_id' => $user->id, 68 | 'personal_company' => true, 69 | ]) 70 | ->when(is_callable($callback), $callback), 71 | 'ownedCompanies' 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 17 | $table->string('name'); 18 | $table->string('email')->unique(); 19 | $table->timestamp('email_verified_at')->nullable(); 20 | $table->string('password')->nullable( 21 | FilamentCompanies::hasSocialiteFeatures() 22 | ); 23 | $table->rememberToken(); 24 | $table->foreignId('current_company_id')->nullable(); 25 | $table->foreignId('current_connected_account_id')->nullable(); 26 | $table->string('profile_photo_path', 2048)->nullable(); 27 | $table->timestamps(); 28 | }); 29 | 30 | Schema::create('password_reset_tokens', function (Blueprint $table) { 31 | $table->string('email')->primary(); 32 | $table->string('token'); 33 | $table->timestamp('created_at')->nullable(); 34 | }); 35 | 36 | Schema::create('sessions', function (Blueprint $table) { 37 | $table->string('id')->primary(); 38 | $table->foreignId('user_id')->nullable()->index(); 39 | $table->string('ip_address', 45)->nullable(); 40 | $table->text('user_agent')->nullable(); 41 | $table->longText('payload'); 42 | $table->integer('last_activity')->index(); 43 | }); 44 | } 45 | 46 | /** 47 | * Reverse the migrations. 48 | */ 49 | public function down(): void 50 | { 51 | Schema::dropIfExists('users'); 52 | Schema::dropIfExists('password_reset_tokens'); 53 | Schema::dropIfExists('sessions'); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /database/migrations/2020_05_21_100000_create_companies_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id')->index(); 17 | $table->string('name'); 18 | $table->boolean('personal_company'); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::dropIfExists('companies'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2020_05_21_200000_create_company_user_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('company_id'); 17 | $table->foreignId('user_id'); 18 | $table->string('role')->nullable(); 19 | $table->timestamps(); 20 | 21 | $table->unique(['company_id', 'user_id']); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('company_user'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2020_05_21_300000_create_company_invitations_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('company_id')->constrained()->cascadeOnDelete(); 17 | $table->string('email'); 18 | $table->string('role')->nullable(); 19 | $table->timestamps(); 20 | 21 | $table->unique(['company_id', 'email']); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('company_invitations'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2020_12_22_000000_create_connected_accounts_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id'); 17 | $table->string('provider'); 18 | $table->string('provider_id'); 19 | $table->string('name')->nullable(); 20 | $table->string('nickname')->nullable(); 21 | $table->string('email')->nullable(); 22 | $table->string('telephone')->nullable(); 23 | $table->text('avatar_path')->nullable(); 24 | $table->string('token', 1000); 25 | $table->string('secret')->nullable(); // OAuth1 26 | $table->string('refresh_token', 1000)->nullable(); // OAuth2 27 | $table->dateTime('expires_at')->nullable(); // OAuth2 28 | $table->timestamps(); 29 | 30 | $table->index(['user_id', 'id']); 31 | $table->index(['provider', 'provider_id']); 32 | }); 33 | } 34 | 35 | /** 36 | * Reverse the migrations. 37 | */ 38 | public function down(): void 39 | { 40 | Schema::dropIfExists('connected_accounts'); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | withPersonalCompany()->create(); 17 | 18 | User::factory()->withPersonalCompany()->create([ 19 | 'name' => 'Test User', 20 | 'email' => 'test@example.com', 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "blank_line_before_statement": true, 5 | "concat_space": { 6 | "spacing": "one" 7 | }, 8 | "method_argument_space": true, 9 | "nullable_type_declaration_for_default_null_value": { 10 | "use_nullable_type_declaration": true 11 | }, 12 | "single_trait_insert_per_statement": true, 13 | "types_spaces": { 14 | "space": "single" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @if (filament()->hasRegistration()) 3 | 4 | {{ __('filament-panels::pages/auth/login.actions.register.before') }} 5 | 6 | {{ $this->registerAction }} 7 | 8 | @endif 9 | 10 | {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::AUTH_LOGIN_FORM_BEFORE, scopes: $this->getRenderHookScopes()) }} 11 | 12 | 13 | {{ $this->form }} 14 | 15 | 19 | 20 | 21 | @if (Wallo\FilamentCompanies\FilamentCompanies::hasSocialiteFeatures()) 22 | 23 | @endif 24 | 25 | {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::AUTH_LOGIN_FORM_AFTER, scopes: $this->getRenderHookScopes()) }} 26 | 27 | -------------------------------------------------------------------------------- /resources/views/auth/policy.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | {!! $policy !!} 4 |
5 |
6 | -------------------------------------------------------------------------------- /resources/views/auth/register.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @if (filament()->hasLogin()) 3 | 4 | {{ __('filament-panels::pages/auth/register.actions.login.before') }} 5 | 6 | {{ $this->loginAction }} 7 | 8 | @endif 9 | 10 | {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::AUTH_REGISTER_FORM_BEFORE, scopes: $this->getRenderHookScopes()) }} 11 | 12 | 13 | {{ $this->form }} 14 | 15 | 19 | 20 | 21 | {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::AUTH_REGISTER_FORM_AFTER, scopes: $this->getRenderHookScopes()) }} 22 | 23 | @if (\Wallo\FilamentCompanies\FilamentCompanies::hasSocialiteFeatures()) 24 | 25 | @endif 26 | 27 | -------------------------------------------------------------------------------- /resources/views/auth/terms.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | {!! $terms !!} 4 |
5 |
6 | -------------------------------------------------------------------------------- /resources/views/companies/delete-company-form.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $modals = \Wallo\FilamentCompanies\FilamentCompanies::getModals(); 3 | @endphp 4 | 5 | 6 | 7 | {{ __('filament-companies::default.action_section_titles.delete_company') }} 8 | 9 | 10 | 11 | {{ __('filament-companies::default.action_section_descriptions.delete_company') }} 12 | 13 | 14 | 15 |
16 |
17 | {{ __('filament-companies::default.subheadings.companies.delete_company') }} 18 |
19 | 20 | 21 | 22 | 23 |
24 | 25 | {{ __('filament-companies::default.buttons.delete_company') }} 26 | 27 |
28 |
29 | 30 | 31 | {{ __('filament-companies::default.modal_titles.delete_company') }} 32 | 33 | 34 | 35 | {{ __('filament-companies::default.modal_descriptions.delete_company') }} 36 | 37 | 38 | 39 | @if($modals['cancelButtonAction']) 40 | 41 | {{ __('filament-companies::default.buttons.cancel') }} 42 | 43 | @endif 44 | 45 | 46 | {{ __('filament-companies::default.buttons.delete_company') }} 47 | 48 | 49 |
50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /resources/views/companies/update-company-name-form.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('filament-companies::default.grid_section_titles.company_name') }} 4 | 5 | 6 | 7 | {{ __('filament-companies::default.grid_section_descriptions.company_name') }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | {{ __('filament-companies::default.labels.company_owner') }} 15 | 16 | 17 |
18 |
19 | 20 |
21 |
22 |
{{ $company->owner->name }}
23 |
{{ $company->owner->email }}
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | @if (Gate::check('update', $company)) 35 |
36 | 37 | {{ __('filament-companies::default.buttons.save') }} 38 | 39 |
40 | @endif 41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /resources/views/components/connected-account.blade.php: -------------------------------------------------------------------------------- 1 | @props(['provider', 'createdAt' => null]) 2 | 3 | @php 4 | $providerEnum = \Wallo\FilamentCompanies\Enums\Provider::tryFrom($provider); 5 | @endphp 6 | 7 | @if($providerEnum?->isEnabled()) 8 |
9 |
10 | 36 | 37 |
38 | {{ $action }} 39 |
40 |
41 | 42 | @error($provider.'_connect_error') 43 |
44 | {{ $message }} 45 |
46 | @enderror 47 |
48 | @endif 49 | -------------------------------------------------------------------------------- /resources/views/components/grid-section.blade.php: -------------------------------------------------------------------------------- 1 | @props(['title','description']) 2 | 3 | 4 | 5 |

{{$title}}

6 | 7 |

8 | {{$description}} 9 |

10 |
11 | 12 | 13 | {{ $slot }} 14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /resources/views/components/input-error.blade.php: -------------------------------------------------------------------------------- 1 | @props(['for']) 2 | 3 | @error($for) 4 |

merge(['class' => 'filament-companies-input-error text-sm text-danger-600 dark:text-danger-400']) }}>{{ $message }}

5 | @enderror 6 | -------------------------------------------------------------------------------- /resources/views/components/input.blade.php: -------------------------------------------------------------------------------- 1 | @props(['disabled' => false, 'id' => null]) 2 | 3 | class([ 6 | 'filament-companies-input block w-full transition duration-75 rounded-lg shadow-sm outline-none focus:ring-1 focus:ring-inset disabled:opacity-70', 7 | 'dark:bg-gray-700 dark:text-white' => config('forms.dark_mode'), 8 | 'border-gray-300 focus:border-primary-500 focus:ring-primary-500' => ! $errors->has($id), 9 | 'dark:border-gray-600 dark:focus:border-primary-500' => ! $errors->has($id) && config('forms.dark_mode'), 10 | 'border-danger-600 ring-danger-600 focus:border-danger-500 focus:ring-danger-500' => $errors->has($id), 11 | 'dark:border-danger-400 dark:ring-danger-400 dark:focus:border-danger-400 dark:focus:ring-danger-400' => $errors->has($id) && config('forms.dark_mode'), 12 | ]) !!} 13 | /> 14 | -------------------------------------------------------------------------------- /resources/views/components/section-border.blade.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /resources/views/components/socialite-icons/bitbucket.blade.php: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /resources/views/components/socialite-icons/facebook.blade.php: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /resources/views/components/socialite-icons/github.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/components/socialite-icons/gitlab.blade.php: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /resources/views/components/socialite-icons/google.blade.php: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /resources/views/components/socialite-icons/linkedin.blade.php: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /resources/views/components/socialite-icons/slack.blade.php: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /resources/views/components/socialite-icons/twitter.blade.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /resources/views/components/socialite.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'errorMessage' => null, 3 | ]) 4 | 5 |
6 |
7 |
8 | {{ __('filament-companies::default.subheadings.auth.login') }} 9 |
10 |
11 | 12 | @if ($errorMessage) 13 |
{!! $errorMessage !!}
14 | @endif 15 | 16 |
17 | @foreach (\Wallo\FilamentCompanies\Enums\Provider::cases() as $provider) 18 | @if ($provider->isEnabled()) 19 | 21 | {{ $provider->getLabel() }} 22 |
23 | {{ $provider->getIconView() }} 24 |
25 |
26 | @endif 27 | @endforeach 28 |
29 |
30 | 31 | -------------------------------------------------------------------------------- /resources/views/filament/pages/companies/company_settings.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @livewire(\Wallo\FilamentCompanies\Http\Livewire\UpdateCompanyNameForm::class, compact('company')) 3 | 4 | @livewire(\Wallo\FilamentCompanies\Http\Livewire\CompanyEmployeeManager::class, compact('company')) 5 | 6 | @if (!$company->personal_company && Gate::check('delete', $company)) 7 | 8 | @livewire(\Wallo\FilamentCompanies\Http\Livewire\DeleteCompanyForm::class, compact('company')) 9 | @endif 10 | 11 | -------------------------------------------------------------------------------- /resources/views/filament/pages/companies/create_company.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ $this->form }} 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /resources/views/filament/pages/user/personal-access-tokens.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $modals = \Wallo\FilamentCompanies\FilamentCompanies::getModals(); 3 | @endphp 4 | 5 | 6 | {{ $this->table }} 7 | 8 | 9 | 10 | {{ __('filament-companies::default.modal_titles.token') }} 11 | 12 | 13 | 14 |
15 | {{ __('filament-companies::default.modal_descriptions.copy_token') }} 16 |
17 |
18 | 19 | 24 | 25 | @if($modals['cancelButtonAction']) 26 | 27 | 28 | {{ __('filament-companies::default.buttons.close') }} 29 | 30 | 31 | @endif 32 |
33 |
34 | -------------------------------------------------------------------------------- /resources/views/filament/pages/user/profile.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @php 3 | $components = \Wallo\FilamentCompanies\FilamentCompanies::getProfileComponents(); 4 | @endphp 5 | 6 | @foreach($components as $index => $component) 7 | @livewire($component) 8 | 9 | @if($loop->remaining) 10 | 11 | @endif 12 | @endforeach 13 | 14 | -------------------------------------------------------------------------------- /resources/views/mail/company-invitation.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | {{ __('You have been invited to join the :company company!', ['company' => $invitation->company->name]) }} 3 | 4 | @if (filament()->getRegistrationUrl()) 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 company invitation:') }} 6 | 7 | @component('mail::button', ['url' => url(filament()->getRegistrationUrl())]) 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 company, you may discard this email.') }} 23 | @endcomponent 24 | -------------------------------------------------------------------------------- /resources/views/profile/delete-user-form.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $modals = \Wallo\FilamentCompanies\FilamentCompanies::getModals(); 3 | @endphp 4 | 5 | 6 | 7 | {{ __('filament-companies::default.grid_section_titles.delete_account') }} 8 | 9 | 10 | 11 | {{ __('filament-companies::default.grid_section_descriptions.delete_account') }} 12 | 13 | 14 | 15 |
16 |

17 | {{ __('filament-companies::default.subheadings.profile.delete_user') }} 18 |

19 | 20 | 21 | 22 | 23 |
24 | 25 | {{ __('filament-companies::default.buttons.delete_account') }} 26 | 27 |
28 |
29 | 30 | 31 | {{ __('filament-companies::default.modal_titles.delete_account') }} 32 | 33 | 34 | 35 | {{ __('filament-companies::default.modal_descriptions.delete_account') }} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | @if($modals['cancelButtonAction']) 46 | 47 | {{ __('filament-companies::default.buttons.cancel') }} 48 | 49 | @endif 50 | 51 | 52 | {{ __('filament-companies::default.buttons.delete_account') }} 53 | 54 | 55 |
56 |
57 |
58 |
59 | -------------------------------------------------------------------------------- /resources/views/profile/set-password-form.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('filament-companies::default.grid_section_titles.set_password') }} 4 | 5 | 6 | 7 | {{ __('filament-companies::default.grid_section_descriptions.set_password') }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | {{ __('filament-companies::default.buttons.save') }} 27 | 28 |
29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /resources/views/profile/update-password-form.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('filament-companies::default.grid_section_titles.update_password') }} 4 | 5 | 6 | 7 | {{ __('filament-companies::default.grid_section_descriptions.update_password') }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | {{ __('filament-companies::default.buttons.save') }} 34 | 35 |
36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /src/Actions/GenerateRedirectForProvider.php: -------------------------------------------------------------------------------- 1 | redirect(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Actions/UpdateCompanyEmployeeRole.php: -------------------------------------------------------------------------------- 1 | authorize('updateCompanyEmployee', $company); 22 | 23 | Validator::make(compact('role'), [ 24 | 'role' => ['required', 'string', new Role], 25 | ])->validate(); 26 | 27 | $company->users()->updateExistingPivot($companyEmployeeId, compact('role')); 28 | 29 | CompanyEmployeeUpdated::dispatch($company->fresh(), FilamentCompanies::findUserByIdOrFail($companyEmployeeId)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Actions/ValidateCompanyDeletion.php: -------------------------------------------------------------------------------- 1 | authorize('delete', $company); 19 | 20 | if ($company->personal_company) { 21 | throw ValidationException::withMessages([ 22 | 'company' => __('filament-companies::default.errors.company_deletion'), 23 | ])->errorBag('deleteCompany'); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Company.php: -------------------------------------------------------------------------------- 1 | belongsTo(FilamentCompanies::userModel(), 'user_id'); 19 | } 20 | 21 | /** 22 | * Get all the company's users including its owner. 23 | */ 24 | public function allUsers(): Collection 25 | { 26 | return $this->users->merge([$this->owner]); 27 | } 28 | 29 | /** 30 | * Get all the users that belong to the company. 31 | */ 32 | public function users(): BelongsToMany 33 | { 34 | return $this->belongsToMany(FilamentCompanies::userModel(), FilamentCompanies::employeeshipModel()) 35 | ->withPivot('role') 36 | ->withTimestamps() 37 | ->as('employeeship'); 38 | } 39 | 40 | /** 41 | * Determine if the given user belongs to the company. 42 | */ 43 | public function hasUser(mixed $user): bool 44 | { 45 | return $this->users->contains($user) || $user->ownsCompany($this); 46 | } 47 | 48 | /** 49 | * Determine if the given email address belongs to a user on the company. 50 | */ 51 | public function hasUserWithEmail(string $email): bool 52 | { 53 | return $this->allUsers()->contains(static function ($user) use ($email) { 54 | return $user->email === $email; 55 | }); 56 | } 57 | 58 | /** 59 | * Determine if the given user has the given permission on the company. 60 | */ 61 | public function userHasPermission(mixed $user, string $permission): bool 62 | { 63 | return $user->hasCompanyPermission($this, $permission); 64 | } 65 | 66 | /** 67 | * Get all the pending user invitations for the company. 68 | */ 69 | public function companyInvitations(): HasMany 70 | { 71 | return $this->hasMany(FilamentCompanies::companyInvitationModel()); 72 | } 73 | 74 | /** 75 | * Remove the given user from the company. 76 | */ 77 | public function removeUser(mixed $user): void 78 | { 79 | if ($user->current_company_id === $this->id) { 80 | $user->forceFill([ 81 | 'current_company_id' => null, 82 | ])->save(); 83 | } 84 | 85 | $this->users()->detach($user); 86 | } 87 | 88 | /** 89 | * Purge all the company's resources. 90 | */ 91 | public function purge(): void 92 | { 93 | $this->owner()->where('current_company_id', $this->id) 94 | ->update(['current_company_id' => null]); 95 | 96 | $this->users()->where('current_company_id', $this->id) 97 | ->update(['current_company_id' => null]); 98 | 99 | $this->users()->detach(); 100 | 101 | $this->delete(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Concerns/Base/HasAddedProfileComponents.php: -------------------------------------------------------------------------------- 1 | $component) { 12 | static::$addedProfileComponents[] = $component; 13 | static::$componentSortOrder[$component] = $sort; 14 | } 15 | 16 | return $this; 17 | } 18 | 19 | /** 20 | * Get the added profile page components. 21 | */ 22 | public static function getAddedProfileComponents(): array 23 | { 24 | return static::$addedProfileComponents; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Concerns/Base/HasAutoAcceptInvitations.php: -------------------------------------------------------------------------------- 1 | singleton(CreatesNewUsers::class, $class); 24 | } 25 | 26 | /** 27 | * Register a class / callback that should be used to update user profile information. 28 | */ 29 | public static function updateUserProfileInformationUsing(string $class): void 30 | { 31 | app()->singleton(UpdatesUserProfileInformation::class, $class); 32 | } 33 | 34 | /** 35 | * Register a class / callback that should be used to update user passwords. 36 | */ 37 | public static function updateUserPasswordsUsing(string $class): void 38 | { 39 | app()->singleton(UpdatesUserPasswords::class, $class); 40 | } 41 | 42 | /** 43 | * Register a class / callback that should be used to create companies. 44 | */ 45 | public static function createCompaniesUsing(string $class): void 46 | { 47 | app()->singleton(CreatesCompanies::class, $class); 48 | } 49 | 50 | /** 51 | * Register a class / callback that should be used to update company names. 52 | */ 53 | public static function updateCompanyNamesUsing(string $class): void 54 | { 55 | app()->singleton(UpdatesCompanyNames::class, $class); 56 | } 57 | 58 | /** 59 | * Register a class / callback that should be used to add company employees. 60 | */ 61 | public static function addCompanyEmployeesUsing(string $class): void 62 | { 63 | app()->singleton(AddsCompanyEmployees::class, $class); 64 | } 65 | 66 | /** 67 | * Register a class / callback that should be used to add company employees. 68 | */ 69 | public static function inviteCompanyEmployeesUsing(string $class): void 70 | { 71 | app()->singleton(InvitesCompanyEmployees::class, $class); 72 | } 73 | 74 | /** 75 | * Register a class / callback that should be used to remove company employees. 76 | */ 77 | public static function removeCompanyEmployeesUsing(string $class): void 78 | { 79 | app()->singleton(RemovesCompanyEmployees::class, $class); 80 | } 81 | 82 | /** 83 | * Register a class / callback that should be used to delete companies. 84 | */ 85 | public static function deleteCompaniesUsing(string $class): void 86 | { 87 | app()->singleton(DeletesCompanies::class, $class); 88 | } 89 | 90 | /** 91 | * Register a class / callback that should be used to delete users. 92 | */ 93 | public static function deleteUsersUsing(string $class): void 94 | { 95 | app()->singleton(DeletesUsers::class, $class); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Concerns/Base/HasBaseModels.php: -------------------------------------------------------------------------------- 1 | where('id', $id)->firstOrFail(); 130 | } 131 | 132 | /** 133 | * Find a user instance by the given email address or fail. 134 | */ 135 | public static function findUserByEmailOrFail(string $email): mixed 136 | { 137 | return static::newUserModel()->where('email', $email)->firstOrFail(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Concerns/Base/HasBaseProfileComponents.php: -------------------------------------------------------------------------------- 1 | user(); 72 | $passwordIsSet = $user?->getAuthPassword() !== null; 73 | 74 | if (static::canUpdateProfileInformation()) { 75 | $components[] = static::getUpdateProfileInformationForm(); 76 | } 77 | 78 | if ($passwordIsSet && static::canUpdatePasswords()) { 79 | $components[] = static::getUpdatePasswordForm(); 80 | } 81 | 82 | if ($passwordIsSet && static::canManageBrowserSessions()) { 83 | $components[] = static::getLogoutOtherBrowserSessionsForm(); 84 | } 85 | 86 | if ($passwordIsSet && static::hasAccountDeletionFeatures()) { 87 | $components[] = static::getDeleteUserForm(); 88 | } 89 | 90 | return $components; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Concerns/Base/HasCompanyFeatures.php: -------------------------------------------------------------------------------- 1 | 0; 30 | } 31 | 32 | /** 33 | * Find the role with the given key. 34 | */ 35 | public static function findRole(string $key): ?Role 36 | { 37 | return static::$roles[$key] ?? null; 38 | } 39 | 40 | /** 41 | * Define a role. 42 | */ 43 | public static function role(string $key, string $name, array $permissions): Role 44 | { 45 | static::$permissions = collect([...static::$permissions, ...$permissions]) 46 | ->unique() 47 | ->sort() 48 | ->values() 49 | ->all(); 50 | 51 | return tap(new Role($key, $name, $permissions), static function ($role) use ($key) { 52 | static::$roles[$key] = $role; 53 | }); 54 | } 55 | 56 | /** 57 | * Determine if any permissions have been registered with Company. 58 | */ 59 | public static function hasPermissions(): bool 60 | { 61 | return count(static::$permissions) > 0; 62 | } 63 | 64 | /** 65 | * Define the available API token permissions. 66 | */ 67 | public static function permissions(array $permissions): static 68 | { 69 | static::$permissions = $permissions; 70 | 71 | return new static; 72 | } 73 | 74 | /** 75 | * Define the default permissions that should be available to new API tokens. 76 | */ 77 | public static function defaultApiTokenPermissions(array $permissions): static 78 | { 79 | static::$defaultPermissions = $permissions; 80 | 81 | return new static; 82 | } 83 | 84 | /** 85 | * Return the permissions in the given list that are actually defined permissions for the application. 86 | */ 87 | public static function validPermissions(array $permissions): array 88 | { 89 | return array_values(array_intersect($permissions, static::$permissions)); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Concerns/Base/HasRoutes.php: -------------------------------------------------------------------------------- 1 | name('oauth.redirect'); 34 | Route::get('/oauth/{provider}/callback', [OAuthController::class, 'handleProviderCallback'])->name('oauth.callback'); 35 | } 36 | 37 | if (static::hasTermsAndPrivacyPolicyFeature()) { 38 | Route::get(Terms::getSlug(), Terms::class)->name(Terms::getRouteName()); 39 | Route::get(PrivacyPolicy::getSlug(), PrivacyPolicy::class)->name(PrivacyPolicy::getRouteName()); 40 | } 41 | } 42 | 43 | protected function registerAuthenticatedRoutes(): void 44 | { 45 | if (static::sendsCompanyInvitations()) { 46 | Route::get('/invitations/{invitation}', [CompanyInvitationController::class, 'accept']) 47 | ->middleware(['signed']) 48 | ->name('invitations.accept'); 49 | } 50 | } 51 | 52 | public static function route(string $name, mixed $parameters = [], bool $absolute = true): string 53 | { 54 | return route(static::generateRouteName($name), $parameters, $absolute); 55 | } 56 | 57 | public static function generateRouteName(string $name): string 58 | { 59 | return 'filament.' . static::getCompanyPanel() . ".{$name}"; 60 | } 61 | 62 | public static function generateOAuthRedirectUrl(string $provider): string 63 | { 64 | return static::route('oauth.redirect', compact('provider')); 65 | } 66 | 67 | public static function generateAcceptInvitationUrl(CompanyInvitation $invitation): string 68 | { 69 | return URL::signedRoute(static::generateRouteName('invitations.accept'), compact('invitation')); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Concerns/Base/HasTermsAndPrivacyPolicy.php: -------------------------------------------------------------------------------- 1 | getLocale() . '$1', $name); 39 | 40 | return Arr::first([ 41 | resource_path('markdown/' . $localName), 42 | resource_path('markdown/' . $name), 43 | ], static function ($path) { 44 | return file_exists($path); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Concerns/ManagesProfileComponents.php: -------------------------------------------------------------------------------- 1 | static::$componentSortOrder[$b]; 22 | }); 23 | 24 | return $components; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Concerns/Socialite/CanEnableSocialite.php: -------------------------------------------------------------------------------- 1 | enableSocialite($condition); 20 | $this->setProviders($providers); 21 | $this->setFeatures($features); 22 | 23 | return $this; 24 | } 25 | 26 | public function enableSocialite(bool | Closure | null $condition = true): static 27 | { 28 | $isEnabled = $condition instanceof Closure ? $condition() : $condition; 29 | static::$hasSocialiteFeatures = $isEnabled; 30 | 31 | if (! $isEnabled) { 32 | static::disableAllSocialiteFeatures(); 33 | } 34 | 35 | return $this; 36 | } 37 | 38 | private static function disableAllSocialiteFeatures(): void 39 | { 40 | static::$hasSocialiteFeatures = false; 41 | static::$canSetPasswords = false; 42 | static::$canManageConnectedAccounts = false; 43 | } 44 | 45 | /** 46 | * Determine if the application has support for socialite. 47 | */ 48 | public static function hasSocialiteFeatures(): bool 49 | { 50 | return static::$hasSocialiteFeatures; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Concerns/Socialite/HasConnectedAccountModel.php: -------------------------------------------------------------------------------- 1 | where('provider', $provider) 49 | ->where('provider_id', $providerId) 50 | ->first(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Concerns/Socialite/HasProviderFeatures.php: -------------------------------------------------------------------------------- 1 | $feature->value, static::$enabledFeatures); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Concerns/Socialite/HasProviders.php: -------------------------------------------------------------------------------- 1 | $provider->value, static::$enabledProviders); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Concerns/Socialite/HasSocialiteActionBindings.php: -------------------------------------------------------------------------------- 1 | singleton(ResolvesSocialiteUsers::class, $class); 21 | } 22 | 23 | /** 24 | * Register a class / callback that should be used to create users from social providers. 25 | */ 26 | public static function createUsersFromProviderUsing(string $class): void 27 | { 28 | app()->singleton(CreatesUserFromProvider::class, $class); 29 | } 30 | 31 | /** 32 | * Register a class / callback that should be used to create connected accounts. 33 | */ 34 | public static function createConnectedAccountsUsing(string $class): void 35 | { 36 | app()->singleton(CreatesConnectedAccounts::class, $class); 37 | } 38 | 39 | /** 40 | * Register a class / callback that should be used to update connected accounts. 41 | */ 42 | public static function updateConnectedAccountsUsing(string $class): void 43 | { 44 | app()->singleton(UpdatesConnectedAccounts::class, $class); 45 | } 46 | 47 | /** 48 | * Register a class / callback that should be used to set user passwords. 49 | */ 50 | public static function setUserPasswordsUsing(callable | string $callback): void 51 | { 52 | app()->singleton(SetsUserPasswords::class, $callback); 53 | } 54 | 55 | /** 56 | * Register a class / callback that should be used to set user passwords. 57 | */ 58 | public static function handlesInvalidStateUsing(callable | string $callback): void 59 | { 60 | app()->singleton(HandlesInvalidState::class, $callback); 61 | } 62 | 63 | /** 64 | * Register a class / callback that should be used for generating provider redirects. 65 | */ 66 | public static function generatesProvidersRedirectsUsing(callable | string $callback): void 67 | { 68 | app()->singleton(GeneratesProviderRedirect::class, $callback); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Concerns/Socialite/HasSocialiteComponents.php: -------------------------------------------------------------------------------- 1 | user(); 41 | $passwordIsNull = $user?->getAuthPassword() === null; 42 | 43 | if ($passwordIsNull && static::canSetPasswords()) { 44 | $components[] = static::getSetPasswordForm(); 45 | } 46 | 47 | if (static::canManageConnectedAccounts()) { 48 | $components[] = static::getConnectedAccountsForm(); 49 | } 50 | 51 | return $components; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Concerns/Socialite/HasSocialiteProfileFeatures.php: -------------------------------------------------------------------------------- 1 | belongsTo(FilamentCompanies::userModel(), 'user_id', FilamentCompanies::newUserModel()->getAuthIdentifierName()); 35 | } 36 | 37 | /** 38 | * Get the data that should be shared. 39 | * 40 | * @return array 41 | */ 42 | public function getSharedData(): array 43 | { 44 | return [ 45 | 'id' => $this->id, 46 | 'provider' => $this->provider, 47 | 'avatar_path' => $this->avatar_path, 48 | 'created_at' => $this->created_at?->diffForHumans(), 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Contracts/AddsCompanyEmployees.php: -------------------------------------------------------------------------------- 1 | $input 13 | */ 14 | public function create(array $input): User; 15 | } 16 | -------------------------------------------------------------------------------- /src/Contracts/CreatesUserFromProvider.php: -------------------------------------------------------------------------------- 1 | id = $connectedAccount->provider_id; 47 | $this->token = $connectedAccount->token; 48 | $this->tokenSecret = $connectedAccount->secret; 49 | $this->refreshToken = $connectedAccount->refresh_token; 50 | $this->expiry = $connectedAccount->expires_at; 51 | } 52 | 53 | /** 54 | * Get the ID for the credentials. 55 | */ 56 | public function getId(): string 57 | { 58 | return $this->id; 59 | } 60 | 61 | /** 62 | * Get token for the credentials. 63 | */ 64 | public function getToken(): string 65 | { 66 | return $this->token; 67 | } 68 | 69 | /** 70 | * Get the token secret for the credentials. 71 | */ 72 | public function getTokenSecret(): ?string 73 | { 74 | return $this->tokenSecret; 75 | } 76 | 77 | /** 78 | * Get the refresh token for the credentials. 79 | */ 80 | public function getRefreshToken(): ?string 81 | { 82 | return $this->refreshToken; 83 | } 84 | 85 | /** 86 | * Get the expiry date for the credentials. 87 | * 88 | * @throws Exception 89 | */ 90 | public function getExpiry(): ?DateTimeInterface 91 | { 92 | if ($this->expiry === null) { 93 | return null; 94 | } 95 | 96 | return new DateTime($this->expiry); 97 | } 98 | 99 | /** 100 | * Get the instance as an array. 101 | * 102 | * @return array 103 | * 104 | * @throws Exception 105 | */ 106 | public function toArray(): array 107 | { 108 | return [ 109 | 'id' => $this->getId(), 110 | 'token' => $this->getToken(), 111 | 'token_secret' => $this->getTokenSecret(), 112 | 'refresh_token' => $this->getRefreshToken(), 113 | 'expiry' => $this->getExpiry(), 114 | ]; 115 | } 116 | 117 | /** 118 | * Convert the object to its JSON representation. 119 | * 120 | * @param int $options 121 | * 122 | * @throws Exception 123 | */ 124 | public function toJson($options = 0): string 125 | { 126 | return json_encode($this->toArray(), JSON_THROW_ON_ERROR | $options); 127 | } 128 | 129 | /** 130 | * Specify data which should be serialized to JSON. 131 | * 132 | * @return array 133 | * 134 | * @throws Exception 135 | */ 136 | public function jsonSerialize(): array 137 | { 138 | return $this->toArray(); 139 | } 140 | 141 | /** 142 | * Convert the object instance to a string. 143 | * 144 | * @throws JsonException 145 | * @throws Exception 146 | */ 147 | public function __toString(): string 148 | { 149 | return json_encode($this->toJson(), JSON_THROW_ON_ERROR); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Employeeship.php: -------------------------------------------------------------------------------- 1 | 'Remember Session', 21 | self::RefreshOAuthTokens => 'Refresh OAuth Tokens', 22 | self::ProviderAvatars => 'Provider Avatars', 23 | self::GenerateMissingEmails => 'Generate Missing Emails', 24 | self::LoginOnRegistration => 'Login on Registration', 25 | self::CreateAccountOnFirstLogin => 'Create Account on First Login', 26 | }; 27 | } 28 | 29 | public function isEnabled(): bool 30 | { 31 | return FilamentCompanies::isFeatureEnabled($this); 32 | } 33 | 34 | public function isDisabled(): bool 35 | { 36 | return $this->isEnabled() === false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Enums/Provider.php: -------------------------------------------------------------------------------- 1 | 'Bitbucket', 26 | self::Facebook => 'Facebook', 27 | self::Gitlab => 'GitLab', 28 | self::Github => 'GitHub', 29 | self::Google => 'Google', 30 | self::LinkedIn, self::LinkedInOpenId => 'LinkedIn', 31 | self::Slack => 'Slack', 32 | self::Twitter, self::TwitterOAuth2 => 'X', 33 | }; 34 | } 35 | 36 | public function isEnabled(): bool 37 | { 38 | return FilamentCompanies::isProviderEnabled($this); 39 | } 40 | 41 | public function getIconView(): View 42 | { 43 | $viewName = match ($this) { 44 | self::Bitbucket => 'filament-companies::components.socialite-icons.bitbucket', 45 | self::Facebook => 'filament-companies::components.socialite-icons.facebook', 46 | self::Gitlab => 'filament-companies::components.socialite-icons.gitlab', 47 | self::Github => 'filament-companies::components.socialite-icons.github', 48 | self::Google => 'filament-companies::components.socialite-icons.google', 49 | self::LinkedIn, self::LinkedInOpenId => 'filament-companies::components.socialite-icons.linkedin', 50 | self::Slack => 'filament-companies::components.socialite-icons.slack', 51 | self::Twitter, self::TwitterOAuth2 => 'filament-companies::components.socialite-icons.twitter', 52 | }; 53 | 54 | return view($viewName); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Events/AddingCompany.php: -------------------------------------------------------------------------------- 1 | owner = $owner; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Events/AddingCompanyEmployee.php: -------------------------------------------------------------------------------- 1 | company = $company; 29 | $this->user = $user; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Events/CompanyCreated.php: -------------------------------------------------------------------------------- 1 | company = $company; 29 | $this->user = $user; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Events/CompanyEmployeeRemoved.php: -------------------------------------------------------------------------------- 1 | company = $company; 29 | $this->user = $user; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Events/CompanyEmployeeUpdated.php: -------------------------------------------------------------------------------- 1 | company = $company; 29 | $this->user = $user; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Events/CompanyEvent.php: -------------------------------------------------------------------------------- 1 | company = $company; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Events/CompanyUpdated.php: -------------------------------------------------------------------------------- 1 | connectedAccount = $connectedAccount; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Events/ConnectedAccountUpdated.php: -------------------------------------------------------------------------------- 1 | company = $company; 34 | $this->email = $email; 35 | $this->role = $role; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Events/RemovingCompanyEmployee.php: -------------------------------------------------------------------------------- 1 | company = $company; 29 | $this->user = $user; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/FilamentCompanies.php: -------------------------------------------------------------------------------- 1 | getId(); 59 | 60 | if (static::hasCompanyFeatures()) { 61 | Livewire::component('filament.pages.companies.create_company', CreateCompany::class); 62 | Livewire::component('filament.pages.companies.company_settings', CompanySettings::class); 63 | } 64 | 65 | app()->bind(RegistrationResponseContract::class, FilamentCompaniesRegistrationResponse::class); 66 | 67 | if (static::hasSocialiteFeatures()) { 68 | app()->bind(OAuthController::class, static function (Application $app) { 69 | return new OAuthController( 70 | $app->make(CreatesUserFromProvider::class), 71 | $app->make(CreatesConnectedAccounts::class), 72 | $app->make(UpdatesConnectedAccounts::class), 73 | $app->make(HandlesInvalidState::class), 74 | ); 75 | }); 76 | } 77 | 78 | if (static::$registersRoutes) { 79 | $panel->routes(fn () => $this->registerPublicRoutes()); 80 | $panel->authenticatedRoutes(fn () => $this->registerAuthenticatedRoutes()); 81 | } 82 | } 83 | 84 | public function boot(Panel $panel): void 85 | { 86 | if (static::switchesCurrentCompany()) { 87 | Event::listen(TenantSet::class, SwitchCurrentCompany::class); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/HasConnectedAccounts.php: -------------------------------------------------------------------------------- 1 | id === $this->currentConnectedAccount->id; 17 | } 18 | 19 | /** 20 | * Get the current connected account of the user's context. 21 | */ 22 | public function currentConnectedAccount(): BelongsTo 23 | { 24 | if ($this->current_connected_account_id === null && $this->id) { 25 | $this->switchConnectedAccount( 26 | $this->connectedAccounts()->orderBy('created_at')->first() 27 | ); 28 | } 29 | 30 | return $this->belongsTo(FilamentCompanies::connectedAccountModel(), 'current_connected_account_id'); 31 | } 32 | 33 | /** 34 | * Switch the user's context to the given connected account. 35 | */ 36 | public function switchConnectedAccount(mixed $connectedAccount): bool 37 | { 38 | if (! $this->ownsConnectedAccount($connectedAccount)) { 39 | return false; 40 | } 41 | 42 | $this->forceFill([ 43 | 'current_connected_account_id' => $connectedAccount->id, 44 | ])->save(); 45 | 46 | $this->setRelation('currentConnectedAccount', $connectedAccount); 47 | 48 | return true; 49 | } 50 | 51 | /** 52 | * Determine if the user owns the given connected account. 53 | */ 54 | public function ownsConnectedAccount(mixed $connectedAccount): bool 55 | { 56 | return $this->id === $connectedAccount->user_id; 57 | } 58 | 59 | /** 60 | * Determine if the user has a specific account type. 61 | */ 62 | public function hasTokenFor(string $provider): bool 63 | { 64 | return $this->connectedAccounts->contains('provider', Str::lower($provider)); 65 | } 66 | 67 | /** 68 | * Attempt to retrieve the token for a given provider. 69 | */ 70 | public function getTokenFor(string $provider, ?string $default = null): ?string 71 | { 72 | if ($this->hasTokenFor($provider)) { 73 | return $this->connectedAccounts 74 | ->where('provider', Str::lower($provider)) 75 | ->first() 76 | ->token; 77 | } 78 | 79 | return $default; 80 | } 81 | 82 | /** 83 | * Attempt to find a connected account that belongs to the user, 84 | * for the given provider and ID. 85 | */ 86 | public function getConnectedAccountFor(string $provider, string $id): mixed 87 | { 88 | return $this->connectedAccounts 89 | ->where('provider', $provider) 90 | ->where('provider_id', $id) 91 | ->first(); 92 | } 93 | 94 | /** 95 | * Get all the connected accounts belonging to the user. 96 | */ 97 | public function connectedAccounts(): HasMany 98 | { 99 | return $this->hasMany(FilamentCompanies::connectedAccountModel(), 'user_id', $this->getAuthIdentifierName()); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/HasProfilePhoto.php: -------------------------------------------------------------------------------- 1 | profile_photo_path, function ($previous) use ($photo, $storagePath) { 19 | $this->forceFill([ 20 | 'profile_photo_path' => $photo->storePublicly( 21 | $storagePath, 22 | ['disk' => $this->profilePhotoDisk()] 23 | ), 24 | ])->save(); 25 | 26 | if ($previous) { 27 | Storage::disk($this->profilePhotoDisk())->delete($previous); 28 | } 29 | }); 30 | } 31 | 32 | /** 33 | * Delete the user's profile photo. 34 | */ 35 | public function deleteProfilePhoto(): void 36 | { 37 | if ($this->profile_photo_path === null || ! FilamentCompanies::managesProfilePhotos()) { 38 | return; 39 | } 40 | 41 | Storage::disk($this->profilePhotoDisk())->delete($this->profile_photo_path); 42 | 43 | $this->forceFill([ 44 | 'profile_photo_path' => null, 45 | ])->save(); 46 | } 47 | 48 | /** 49 | * Get the URL to the user's profile photo. 50 | */ 51 | public function profilePhotoUrl(): Attribute 52 | { 53 | return Attribute::get(function () { 54 | return $this->profile_photo_path 55 | ? Storage::disk($this->profilePhotoDisk())->url($this->profile_photo_path) 56 | : $this->defaultProfilePhotoUrl(); 57 | }); 58 | } 59 | 60 | /** 61 | * Get the default profile photo URL if no profile photo has been uploaded. 62 | */ 63 | protected function defaultProfilePhotoUrl(): string 64 | { 65 | $name = trim(collect(explode(' ', $this->name))->map(static function ($segment) { 66 | return mb_substr($segment, 0, 1); 67 | })->join(' ')); 68 | 69 | return sprintf('https://ui-avatars.com/api/?name=%s&color=FFFFFF&background=111827', urlencode($name)); 70 | } 71 | 72 | /** 73 | * Get the disk that profile photos should be stored on. 74 | */ 75 | protected function profilePhotoDisk(): string 76 | { 77 | return isset($_ENV['VAPOR_ARTIFACT_NAME']) ? 's3' : FilamentCompanies::profilePhotoDisk(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Http/Controllers/CompanyInvitationController.php: -------------------------------------------------------------------------------- 1 | firstOrFail(); 27 | $user = FilamentCompanies::userModel()::where('email', $invitation->email)->first(); 28 | 29 | app(AddsCompanyEmployees::class)->add( 30 | $invitation->company->owner, 31 | $invitation->company, 32 | $invitation->email, 33 | $invitation->role 34 | ); 35 | 36 | $invitation->delete(); 37 | 38 | $title = __('filament-companies::default.banner.company_invitation_accepted', ['company' => $invitation->company->name]); 39 | $notification = Notification::make()->title(Str::inlineMarkdown($title))->success()->persistent()->send(); 40 | 41 | if ($user) { 42 | Filament::auth()->login($user); 43 | 44 | return redirect(url(filament()->getHomeUrl()))->with('notification.success.company_invitation_accepted', $notification); 45 | } 46 | 47 | return redirect(url(filament()->getLoginUrl())); 48 | } 49 | 50 | /** 51 | * Cancel the given company invitation. 52 | * 53 | * @throws AuthorizationException 54 | */ 55 | public function destroy(Request $request, int $invitationId): Redirector | RedirectResponse 56 | { 57 | $model = FilamentCompanies::companyInvitationModel(); 58 | 59 | $invitation = $model::whereKey($invitationId)->firstOrFail(); 60 | 61 | if (! Gate::forUser($request->user())->check('removeCompanyEmployee', $invitation->company)) { 62 | throw new AuthorizationException; 63 | } 64 | 65 | $invitation->delete(); 66 | 67 | return back(303); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Http/Livewire/CreateCompanyForm.php: -------------------------------------------------------------------------------- 1 | resetErrorBag(); 32 | 33 | $creator->create($this->user, $this->state); 34 | 35 | $name = $this->state['name']; 36 | 37 | $this->companyCreated($name); 38 | 39 | return $this->redirectPath($creator); 40 | } 41 | 42 | /** 43 | * Get the current user of the application. 44 | */ 45 | public function getUserProperty(): ?Authenticatable 46 | { 47 | return Auth::user(); 48 | } 49 | 50 | /** 51 | * Render the component. 52 | */ 53 | public function render(): View 54 | { 55 | return view('filament-companies::companies.create-company-form'); 56 | } 57 | 58 | public function companyCreated($name): void 59 | { 60 | Notification::make() 61 | ->title(__('filament-companies::default.notifications.company_created.title')) 62 | ->success() 63 | ->body(Str::inlineMarkdown(__('filament-companies::default.notifications.company_created.body', compact('name')))) 64 | ->send(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Http/Livewire/DeleteCompanyForm.php: -------------------------------------------------------------------------------- 1 | company = $company; 34 | } 35 | 36 | /** 37 | * Delete the company. 38 | * 39 | * @throws AuthorizationException 40 | */ 41 | public function deleteCompany(ValidateCompanyDeletion $validator, DeletesCompanies $deleter): Response | Redirector | RedirectResponse 42 | { 43 | $validator->validate(Auth::user(), $this->company); 44 | 45 | $deleter->delete($this->company); 46 | 47 | if (FilamentCompanies::hasNotificationsFeature()) { 48 | if (method_exists($deleter, 'companyDeleted')) { 49 | $deleter->companyDeleted($this->company); 50 | } else { 51 | $this->companyDeleted($this->company); 52 | } 53 | } 54 | 55 | $this->company = null; 56 | 57 | return $this->redirectPath($deleter); 58 | } 59 | 60 | /** 61 | * Cancel the company deletion. 62 | */ 63 | public function cancelCompanyDeletion(): void 64 | { 65 | $this->dispatch('close-modal', id: 'confirmingCompanyDeletion'); 66 | } 67 | 68 | /** 69 | * Render the component. 70 | */ 71 | public function render(): View 72 | { 73 | return view('filament-companies::companies.delete-company-form'); 74 | } 75 | 76 | public function companyDeleted($company): void 77 | { 78 | $name = $company->name; 79 | 80 | Notification::make() 81 | ->title(__('filament-companies::default.notifications.company_deleted.title')) 82 | ->success() 83 | ->body(Str::inlineMarkdown(__('filament-companies::default.notifications.company_deleted.body', compact('name')))) 84 | ->send(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Http/Livewire/DeleteUserForm.php: -------------------------------------------------------------------------------- 1 | resetErrorBag(); 28 | 29 | $this->password = ''; 30 | 31 | $this->dispatch('confirming-delete-user'); 32 | 33 | $this->dispatch('open-modal', id: 'confirmingUserDeletion'); 34 | } 35 | 36 | /** 37 | * Delete the current user. 38 | */ 39 | public function deleteUser(DeletesUsers $deleter): RedirectResponse | Redirector 40 | { 41 | $this->resetErrorBag(); 42 | 43 | $auth = Filament::auth(); 44 | 45 | if (! Hash::check($this->password, Auth::user()->password)) { 46 | throw ValidationException::withMessages([ 47 | 'password' => [__('filament-companies::default.errors.invalid_password')], 48 | ]); 49 | } 50 | 51 | $deleter->delete(Auth::user()?->fresh()); 52 | 53 | $auth->logout(); 54 | 55 | if (session() !== null) { 56 | session()->invalidate(); 57 | session()->regenerateToken(); 58 | } 59 | 60 | return redirect()->to(Filament::hasLogin() ? Filament::getLoginUrl() : Filament::getUrl()); 61 | } 62 | 63 | /** 64 | * Cancel the user deletion. 65 | */ 66 | public function cancelUserDeletion(): void 67 | { 68 | $this->dispatch('close-modal', id: 'confirmingUserDeletion'); 69 | } 70 | 71 | /** 72 | * Render the component. 73 | */ 74 | public function render(): View 75 | { 76 | return view('filament-companies::profile.delete-user-form'); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Http/Livewire/SetPasswordForm.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | public $state = [ 22 | 'password' => '', 23 | 'password_confirmation' => '', 24 | ]; 25 | 26 | /** 27 | * Update the user's password. 28 | */ 29 | public function setPassword(SetsUserPasswords $setter): void 30 | { 31 | $this->resetErrorBag(); 32 | 33 | $setter->set($this->user, $this->state); 34 | 35 | $this->state = [ 36 | 'password' => '', 37 | 'password_confirmation' => '', 38 | ]; 39 | 40 | if (FilamentCompanies::hasNotificationsFeature()) { 41 | if (method_exists($setter, 'passwordSet')) { 42 | $setter->passwordSet($this->user, $this->state); 43 | } else { 44 | $this->passwordSet(); 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * Get the current user of the application. 51 | */ 52 | public function getUserProperty(): ?Authenticatable 53 | { 54 | return Auth::user(); 55 | } 56 | 57 | /** 58 | * Render the component. 59 | */ 60 | public function render(): View 61 | { 62 | return view('filament-companies::profile.set-password-form'); 63 | } 64 | 65 | public function passwordSet(): void 66 | { 67 | Notification::make() 68 | ->title(__('filament-companies::default.notifications.password_set.title')) 69 | ->success() 70 | ->color(Color::Green) 71 | ->body(__('filament-companies::default.notifications.password_set.body')) 72 | ->duration(3000) 73 | ->send(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Http/Livewire/UpdateCompanyNameForm.php: -------------------------------------------------------------------------------- 1 | company = $company; 32 | 33 | $this->state = $company->withoutRelations()->toArray(); 34 | } 35 | 36 | /** 37 | * Update the company's name. 38 | */ 39 | public function updateCompanyName(UpdatesCompanyNames $updater): void 40 | { 41 | $this->resetErrorBag(); 42 | 43 | $updater->update($this->user, $this->company, $this->state); 44 | 45 | if (FilamentCompanies::hasNotificationsFeature()) { 46 | if (method_exists($updater, 'companyNameUpdated')) { 47 | $updater->companyNameUpdated($this->user, $this->company, $this->state); 48 | } else { 49 | $this->companyNameUpdated($this->company); 50 | } 51 | } 52 | } 53 | 54 | protected function companyNameUpdated($company): void 55 | { 56 | $name = $company->name; 57 | 58 | Notification::make() 59 | ->title(__('filament-companies::default.notifications.company_name_updated.title')) 60 | ->success() 61 | ->body(Str::inlineMarkdown(__('filament-companies::default.notifications.company_name_updated.body', compact('name')))) 62 | ->send(); 63 | } 64 | 65 | /** 66 | * Get the current user of the application. 67 | */ 68 | public function getUserProperty(): ?Authenticatable 69 | { 70 | return Auth::user(); 71 | } 72 | 73 | /** 74 | * Render the component. 75 | */ 76 | public function render(): View 77 | { 78 | return view('filament-companies::companies.update-company-name-form'); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Http/Livewire/UpdatePasswordForm.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | public $state = [ 21 | 'current_password' => '', 22 | 'password' => '', 23 | 'password_confirmation' => '', 24 | ]; 25 | 26 | /** 27 | * Update the user's password. 28 | */ 29 | public function updatePassword(UpdatesUserPasswords $updater): void 30 | { 31 | $this->resetErrorBag(); 32 | 33 | $updater->update($this->user, $this->state); 34 | 35 | if (session() !== null) { 36 | session()->put([ 37 | 'password_hash_' . Auth::getDefaultDriver() => $this->user?->getAuthPassword(), 38 | ]); 39 | } 40 | 41 | $this->state = [ 42 | 'current_password' => '', 43 | 'password' => '', 44 | 'password_confirmation' => '', 45 | ]; 46 | 47 | if (FilamentCompanies::hasNotificationsFeature()) { 48 | if (method_exists($updater, 'passwordUpdated')) { 49 | $updater->passwordUpdated($this->user, $this->state); 50 | } else { 51 | $this->passwordUpdated(); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * Get the current user of the application. 58 | */ 59 | public function getUserProperty(): ?Authenticatable 60 | { 61 | return Auth::user(); 62 | } 63 | 64 | /** 65 | * Render the component. 66 | */ 67 | public function render(): View 68 | { 69 | return view('filament-companies::profile.update-password-form'); 70 | } 71 | 72 | public function passwordUpdated(): void 73 | { 74 | Notification::make() 75 | ->title(__('filament-companies::default.notifications.password_updated.title')) 76 | ->success() 77 | ->body(__('filament-companies::default.notifications.password_updated.body')) 78 | ->send(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Http/Livewire/UpdateProfileInformationForm.php: -------------------------------------------------------------------------------- 1 | user; 40 | 41 | $this->state = ['email' => $user?->email, ...$user?->withoutRelations()->toArray()]; 42 | } 43 | 44 | /** 45 | * Update the user's profile information. 46 | */ 47 | public function updateProfileInformation(UpdatesUserProfileInformation $updater): void 48 | { 49 | $this->resetErrorBag(); 50 | 51 | $updater->update( 52 | $this->user, 53 | $this->photo 54 | ? [...$this->state, 'photo' => $this->photo] 55 | : $this->state 56 | ); 57 | 58 | if (isset($this->photo)) { 59 | redirect(Profile::getUrl()); 60 | } 61 | 62 | if (FilamentCompanies::hasNotificationsFeature()) { 63 | if (method_exists($updater, 'profileInformationUpdated')) { 64 | $updater->profileInformationUpdated($this->user, $this->state); 65 | } else { 66 | $this->profileInformationUpdated(); 67 | } 68 | } 69 | } 70 | 71 | protected function profileInformationUpdated(): void 72 | { 73 | Notification::make() 74 | ->title(__('filament-companies::default.notifications.profile_information_updated.title')) 75 | ->success() 76 | ->body(__('filament-companies::default.notifications.profile_information_updated.body')) 77 | ->send(); 78 | } 79 | 80 | /** 81 | * Delete user's profile photo. 82 | */ 83 | public function deleteProfilePhoto(): void 84 | { 85 | $this->user?->deleteProfilePhoto(); 86 | } 87 | 88 | /** 89 | * Sent the email verification. 90 | */ 91 | public function sendEmailVerification(): void 92 | { 93 | $this->user?->sendEmailVerificationNotification(); 94 | 95 | $this->verificationLinkSent = true; 96 | 97 | Notification::make() 98 | ->title(__('filament-companies::default.notifications.verification_link_sent.title')) 99 | ->success() 100 | ->body(__('filament-companies::default.notifications.verification_link_sent.body')) 101 | ->send(); 102 | } 103 | 104 | /** 105 | * Get the current user of the application. 106 | */ 107 | public function getUserProperty(): ?Authenticatable 108 | { 109 | return Auth::user(); 110 | } 111 | 112 | /** 113 | * Render the component. 114 | */ 115 | public function render(): View 116 | { 117 | return view('filament-companies::profile.update-profile-information-form'); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Http/Responses/Auth/FilamentCompaniesRegistrationResponse.php: -------------------------------------------------------------------------------- 1 | user(); 16 | 17 | if ( 18 | FilamentCompanies::autoAcceptsInvitations() && 19 | method_exists($user, 'hasAnyCompanies') && 20 | ! $user->hasAnyCompanies() && 21 | ($invitation = FilamentCompanies::companyInvitationModel()::where('email', $user->email)->first()) 22 | ) { 23 | return redirect(FilamentCompanies::generateAcceptInvitationUrl($invitation)); 24 | } 25 | 26 | return parent::toResponse($request); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Listeners/SwitchCurrentCompany.php: -------------------------------------------------------------------------------- 1 | getTenant(); 25 | 26 | /** @var HasCompanies $user */ 27 | $user = $event->getUser(); 28 | 29 | if (FilamentCompanies::switchesCurrentCompany() === false || ! in_array(HasCompanies::class, class_uses_recursive($user), true)) { 30 | return; 31 | } 32 | 33 | if (! $user->switchCompany($tenant) && ($fallbackCompany = $user->primaryCompany())) { 34 | $user->switchCompany($fallbackCompany); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Mail/CompanyInvitation.php: -------------------------------------------------------------------------------- 1 | invitation = $invitation; 29 | } 30 | 31 | /** 32 | * Build the message. 33 | */ 34 | public function build(): static 35 | { 36 | $acceptUrl = FilamentCompanies::generateAcceptInvitationUrl($this->invitation); 37 | 38 | return $this->markdown('filament-companies::mail.company-invitation', compact('acceptUrl')) 39 | ->subject(__('Company Invitation')); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/OwnerRole.php: -------------------------------------------------------------------------------- 1 | schema([ 17 | $this->getEmailFormComponent(), 18 | $this->getPasswordFormComponent(), 19 | $this->getRememberFormComponent(), 20 | ]) 21 | ->statePath('data') 22 | ->model(FilamentCompanies::userModel()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Pages/Auth/PrivacyPolicy.php: -------------------------------------------------------------------------------- 1 | Str::markdown(file_get_contents($policyFile)), 24 | ]; 25 | } 26 | 27 | public function getHeading(): string | Htmlable 28 | { 29 | return ''; 30 | } 31 | 32 | public function getMaxWidth(): MaxWidth | string | null 33 | { 34 | return MaxWidth::TwoExtraLarge; 35 | } 36 | 37 | public static function getSlug(): string 38 | { 39 | return static::$slug ?? 'privacy-policy'; 40 | } 41 | 42 | public static function getRouteName(): string 43 | { 44 | return 'auth.policy'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Pages/Auth/Register.php: -------------------------------------------------------------------------------- 1 | schema([ 21 | $this->getNameFormComponent(), 22 | $this->getEmailFormComponent(), 23 | $this->getPasswordFormComponent(), 24 | $this->getPasswordConfirmationFormComponent(), 25 | ...FilamentCompanies::hasTermsAndPrivacyPolicyFeature() ? [$this->getTermsFormComponent()] : []]) 26 | ->statePath('data') 27 | ->model(FilamentCompanies::userModel()); 28 | } 29 | 30 | protected function getTermsFormComponent(): Component 31 | { 32 | return Checkbox::make('terms') 33 | ->label(new HtmlString(__('filament-companies::default.subheadings.auth.register', [ 34 | 'terms_of_service' => $this->generateFilamentLink(Terms::getRouteName(), __('filament-companies::default.links.terms_of_service')), 35 | 'privacy_policy' => $this->generateFilamentLink(PrivacyPolicy::getRouteName(), __('filament-companies::default.links.privacy_policy')), 36 | ]))) 37 | ->validationAttribute(__('filament-companies::default.errors.terms')) 38 | ->accepted(); 39 | } 40 | 41 | public function generateFilamentLink(string $routeName, string $label): string 42 | { 43 | return Blade::render('filament::components.link', [ 44 | 'href' => FilamentCompanies::route($routeName), 45 | 'target' => '_blank', 46 | 'color' => 'primary', 47 | 'slot' => $label, 48 | ]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Pages/Auth/Terms.php: -------------------------------------------------------------------------------- 1 | Str::markdown(file_get_contents($termsFile)), 24 | ]; 25 | } 26 | 27 | public function getHeading(): string | Htmlable 28 | { 29 | return ''; 30 | } 31 | 32 | public function getMaxWidth(): MaxWidth | string | null 33 | { 34 | return MaxWidth::TwoExtraLarge; 35 | } 36 | 37 | public static function getSlug(): string 38 | { 39 | return static::$slug ?? 'terms-of-service'; 40 | } 41 | 42 | public static function getRouteName(): string 43 | { 44 | return 'auth.terms'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Pages/Company/CompanySettings.php: -------------------------------------------------------------------------------- 1 | allowed(); 25 | } catch (AuthorizationException $exception) { 26 | return $exception->toResponse()->allowed(); 27 | } 28 | } 29 | 30 | protected function getViewData(): array 31 | { 32 | return [ 33 | 'company' => Filament::getTenant(), 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Pages/Company/CreateCompany.php: -------------------------------------------------------------------------------- 1 | schema([ 29 | TextInput::make('name') 30 | ->label(__('filament-companies::default.labels.company_name')) 31 | ->autofocus() 32 | ->maxLength(255) 33 | ->required(), 34 | ]) 35 | ->model(FilamentCompanies::companyModel()) 36 | ->statePath('data'); 37 | } 38 | 39 | protected function handleRegistration(array $data): Model 40 | { 41 | $user = Auth::user(); 42 | 43 | Gate::forUser($user)->authorize('create', FilamentCompanies::newCompanyModel()); 44 | 45 | AddingCompany::dispatch($user); 46 | 47 | $personalCompany = $user?->personalCompany() === null; 48 | 49 | $company = $user?->ownedCompanies()->create([ 50 | 'name' => $data['name'], 51 | 'personal_company' => $personalCompany, 52 | ]); 53 | 54 | $user?->switchCompany($company); 55 | 56 | $name = $data['name']; 57 | 58 | $this->companyCreated($name); 59 | 60 | return $company; 61 | } 62 | 63 | protected function companyCreated($name): void 64 | { 65 | Notification::make() 66 | ->title(__('filament-companies::default.notifications.company_created.title')) 67 | ->success() 68 | ->body(Str::inlineMarkdown(__('filament-companies::default.notifications.company_created.body', compact('name')))) 69 | ->send(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Pages/User/Profile.php: -------------------------------------------------------------------------------- 1 | Auth::user(), 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/RedirectsActions.php: -------------------------------------------------------------------------------- 1 | redirectTo(); 18 | } else { 19 | $response = property_exists($action, 'redirectTo') 20 | ? $action->redirectTo 21 | : filament()->getHomeUrl(); 22 | } 23 | 24 | return $response instanceof Response ? $response : redirect($response); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Role.php: -------------------------------------------------------------------------------- 1 | key = $key; 37 | $this->name = $name; 38 | $this->permissions = $permissions; 39 | } 40 | 41 | /** 42 | * Describe the role. 43 | * 44 | * @return $this 45 | */ 46 | public function description(string $description): static 47 | { 48 | $this->description = $description; 49 | 50 | return $this; 51 | } 52 | 53 | /** 54 | * Get the JSON serializable representation of the object. 55 | */ 56 | #[\ReturnTypeWillChange] 57 | public function jsonSerialize(): array 58 | { 59 | return [ 60 | 'key' => $this->key, 61 | 'name' => __($this->name), 62 | 'description' => __($this->description), 63 | 'permissions' => $this->permissions, 64 | ]; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Rules/Role.php: -------------------------------------------------------------------------------- 1 | = 200 and < 300 21 | if ($response->successful()) { 22 | file_put_contents($file = sys_get_temp_dir() . '/' . Str::uuid()->toString(), $response); 23 | 24 | $this->updateProfilePhoto(new UploadedFile($file, $name)); 25 | } else { 26 | Notification::make() 27 | ->title('Unable to retrieve image') 28 | ->danger() 29 | ->persistent() 30 | ->send(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/AddCompanyEmployee.php: -------------------------------------------------------------------------------- 1 | authorize('addCompanyEmployee', $company); 28 | 29 | $this->validate($company, $email, $role); 30 | 31 | $newCompanyEmployee = FilamentCompanies::findUserByEmailOrFail($email); 32 | 33 | AddingCompanyEmployee::dispatch($company, $newCompanyEmployee); 34 | 35 | $company->users()->attach( 36 | $newCompanyEmployee, 37 | ['role' => $role] 38 | ); 39 | 40 | CompanyEmployeeAdded::dispatch($company, $newCompanyEmployee); 41 | } 42 | 43 | /** 44 | * Validate the add employee operation. 45 | */ 46 | protected function validate(Company $company, string $email, ?string $role): void 47 | { 48 | Validator::make([ 49 | 'email' => $email, 50 | 'role' => $role, 51 | ], $this->rules(), [ 52 | 'email.exists' => __('filament-companies::default.errors.email_not_found'), 53 | ])->after( 54 | $this->ensureUserIsNotAlreadyOnCompany($company, $email) 55 | )->validateWithBag('addCompanyEmployee'); 56 | } 57 | 58 | /** 59 | * Get the validation rules for adding a company employee. 60 | * 61 | * @return array 62 | */ 63 | protected function rules(): array 64 | { 65 | return array_filter([ 66 | 'email' => ['required', 'email', 'exists:users'], 67 | 'role' => FilamentCompanies::hasRoles() 68 | ? ['required', 'string', new Role] 69 | : null, 70 | ]); 71 | } 72 | 73 | /** 74 | * Ensure that the user is not already on the company. 75 | */ 76 | protected function ensureUserIsNotAlreadyOnCompany(Company $company, string $email): Closure 77 | { 78 | return static function ($validator) use ($company, $email) { 79 | $validator->errors()->addIf( 80 | $company->hasUserWithEmail($email), 81 | 'email', 82 | __('filament-companies::default.errors.user_belongs_to_company') 83 | ); 84 | }; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/CreateCompany.php: -------------------------------------------------------------------------------- 1 | $input 20 | * 21 | * @throws AuthorizationException 22 | */ 23 | public function create(User $user, array $input): Company 24 | { 25 | Gate::forUser($user)->authorize('create', FilamentCompanies::newCompanyModel()); 26 | 27 | Validator::make($input, [ 28 | 'name' => ['required', 'string', 'max:255'], 29 | ])->validateWithBag('createCompany'); 30 | 31 | AddingCompany::dispatch($user); 32 | 33 | $user->switchCompany($company = $user->ownedCompanies()->create([ 34 | 'name' => $input['name'], 35 | 'personal_company' => false, 36 | ])); 37 | 38 | return $company; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/CreateConnectedAccount.php: -------------------------------------------------------------------------------- 1 | $user->getAuthIdentifier(), 20 | 'provider' => strtolower($provider), 21 | 'provider_id' => $providerUser->getId(), 22 | 'name' => $providerUser->getName(), 23 | 'nickname' => $providerUser->getNickname(), 24 | 'email' => $providerUser->getEmail(), 25 | 'avatar_path' => $providerUser->getAvatar(), 26 | 'token' => $providerUser->token, 27 | 'secret' => $providerUser->tokenSecret ?? null, 28 | 'refresh_token' => $providerUser->refreshToken ?? null, 29 | 'expires_at' => property_exists($providerUser, 'expiresIn') ? now()->addSeconds($providerUser->expiresIn) : null, 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/CreateNewUser.php: -------------------------------------------------------------------------------- 1 | $input 19 | */ 20 | public function create(array $input): User 21 | { 22 | Validator::make($input, [ 23 | 'name' => ['required', 'string', 'max:255'], 24 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 25 | 'password' => ['required', 'string', 'min:8', 'confirmed'], 26 | 'terms' => FilamentCompanies::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '', 27 | ])->validate(); 28 | 29 | return DB::transaction(function () use ($input) { 30 | return tap(User::create([ 31 | 'name' => $input['name'], 32 | 'email' => $input['email'], 33 | 'password' => Hash::make($input['password']), 34 | ]), function (User $user) { 35 | $this->createCompany($user); 36 | }); 37 | }); 38 | } 39 | 40 | /** 41 | * Create a personal company for the user. 42 | */ 43 | protected function createCompany(User $user): void 44 | { 45 | $user->ownedCompanies()->save(Company::forceCreate([ 46 | 'user_id' => $user->id, 47 | 'name' => explode(' ', $user->name, 2)[0] . "'s Company", 48 | 'personal_company' => true, 49 | ])); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/CreateUserFromProvider.php: -------------------------------------------------------------------------------- 1 | createsConnectedAccounts = $createsConnectedAccounts; 27 | } 28 | 29 | /** 30 | * Create a new user from a social provider user. 31 | */ 32 | public function create(string $provider, ProviderUserContract $providerUser): User 33 | { 34 | return DB::transaction(function () use ($providerUser, $provider) { 35 | return tap(User::create([ 36 | 'name' => $providerUser->getName(), 37 | 'email' => $providerUser->getEmail(), 38 | ]), function (User $user) use ($providerUser, $provider) { 39 | $user->markEmailAsVerified(); 40 | 41 | if ($this->shouldSetProfilePhoto($providerUser)) { 42 | $user->setProfilePhotoFromUrl($providerUser->getAvatar()); 43 | } 44 | 45 | $user->switchConnectedAccount( 46 | $this->createsConnectedAccounts->create($user, $provider, $providerUser) 47 | ); 48 | 49 | $this->createCompany($user); 50 | }); 51 | }); 52 | } 53 | 54 | private function shouldSetProfilePhoto(ProviderUserContract $providerUser): bool 55 | { 56 | return Feature::ProviderAvatars->isEnabled() && 57 | FilamentCompanies::managesProfilePhotos() && 58 | $providerUser->getAvatar(); 59 | } 60 | 61 | /** 62 | * Create a personal company for the user. 63 | */ 64 | protected function createCompany(User $user): void 65 | { 66 | $user->ownedCompanies()->save(Company::forceCreate([ 67 | 'user_id' => $user->id, 68 | 'name' => explode(' ', $user->name, 2)[0] . "'s Company", 69 | 'personal_company' => true, 70 | ])); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/DeleteCompany.php: -------------------------------------------------------------------------------- 1 | purge(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/DeleteUser.php: -------------------------------------------------------------------------------- 1 | deleteCompanies($user); 29 | $user->deleteProfilePhoto(); 30 | $user->tokens->each(static fn (PersonalAccessToken $token) => $token->delete()); 31 | $user->delete(); 32 | }); 33 | } 34 | 35 | /** 36 | * Delete the companies and company associations attached to the user. 37 | */ 38 | protected function deleteCompanies(User $user): void 39 | { 40 | $user->companies()->detach(); 41 | 42 | $user->ownedCompanies->each(function (Company $company) { 43 | $this->deletesCompanies->delete($company); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/DeleteUserWithSocialite.php: -------------------------------------------------------------------------------- 1 | deletesCompanies = $deletesCompanies; 24 | } 25 | 26 | /** 27 | * Delete the given user. 28 | */ 29 | public function delete(User $user): void 30 | { 31 | DB::transaction(function () use ($user) { 32 | $this->deleteCompanies($user); 33 | $user->deleteProfilePhoto(); 34 | $user->connectedAccounts->each(static fn ($account) => $account->delete()); 35 | $user->tokens->each(static fn ($token) => $token->delete()); 36 | $user->delete(); 37 | }); 38 | } 39 | 40 | /** 41 | * Delete the companies and company associations attached to the user. 42 | */ 43 | protected function deleteCompanies(User $user): void 44 | { 45 | $user->companies()->detach(); 46 | 47 | $user->ownedCompanies->each(function (Company $company) { 48 | $this->deletesCompanies->delete($company); 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/HandleInvalidState.php: -------------------------------------------------------------------------------- 1 | authorize('addCompanyEmployee', $company); 30 | 31 | $this->validate($company, $email, $role); 32 | 33 | InvitingCompanyEmployee::dispatch($company, $email, $role); 34 | 35 | $invitation = $company->companyInvitations()->create([ 36 | 'email' => $email, 37 | 'role' => $role, 38 | ]); 39 | 40 | Mail::to($email)->send(new CompanyInvitation($invitation)); 41 | } 42 | 43 | /** 44 | * Validate the invite employee operation. 45 | */ 46 | protected function validate(Company $company, string $email, ?string $role): void 47 | { 48 | Validator::make([ 49 | 'email' => $email, 50 | 'role' => $role, 51 | ], $this->rules($company), [ 52 | 'email.unique' => __('filament-companies::default.errors.employee_already_invited'), 53 | ])->after( 54 | $this->ensureUserIsNotAlreadyOnCompany($company, $email) 55 | )->validateWithBag('addCompanyEmployee'); 56 | } 57 | 58 | /** 59 | * Get the validation rules for inviting a company employee. 60 | * 61 | * @return array 62 | */ 63 | protected function rules(Company $company): array 64 | { 65 | return array_filter([ 66 | 'email' => [ 67 | 'required', 'email', 68 | Rule::unique('company_invitations')->where(static function (Builder $query) use ($company) { 69 | $query->where('company_id', $company->id); 70 | }), 71 | ], 72 | 'role' => FilamentCompanies::hasRoles() 73 | ? ['required', 'string', new Role] 74 | : null, 75 | ]); 76 | } 77 | 78 | /** 79 | * Ensure that the employee is not already on the company. 80 | */ 81 | protected function ensureUserIsNotAlreadyOnCompany(Company $company, string $email): Closure 82 | { 83 | return static function ($validator) use ($company, $email) { 84 | $validator->errors()->addIf( 85 | $company->hasUserWithEmail($email), 86 | 'email', 87 | __('filament-companies::default.errors.employee_already_belongs_to_company') 88 | ); 89 | }; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/RemoveCompanyEmployee.php: -------------------------------------------------------------------------------- 1 | authorize($user, $company, $companyEmployee); 23 | 24 | $this->ensureUserDoesNotOwnCompany($companyEmployee, $company); 25 | 26 | $company->removeUser($companyEmployee); 27 | 28 | CompanyEmployeeRemoved::dispatch($company, $companyEmployee); 29 | } 30 | 31 | /** 32 | * Authorize that the user can remove the company employee. 33 | * 34 | * @throws AuthorizationException 35 | */ 36 | protected function authorize(User $user, Company $company, User $companyEmployee): void 37 | { 38 | if (! Gate::forUser($user)->check('removeCompanyEmployee', $company) && 39 | $user->id !== $companyEmployee->id) { 40 | throw new AuthorizationException; 41 | } 42 | } 43 | 44 | /** 45 | * Ensure that the currently authenticated user does not own the company. 46 | */ 47 | protected function ensureUserDoesNotOwnCompany(User $companyEmployee, Company $company): void 48 | { 49 | if ($companyEmployee->id === $company->owner->id) { 50 | throw ValidationException::withMessages([ 51 | 'company' => [__('filament-companies::default.errors.cannot_leave_company')], 52 | ])->errorBag('removeCompanyEmployee'); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/ResolveSocialiteUser.php: -------------------------------------------------------------------------------- 1 | user(); 18 | 19 | if (Feature::GenerateMissingEmails->isEnabled()) { 20 | $user->email = $user->getEmail() ?? ("{$user->id}@{$provider}" . config('app.domain')); 21 | } 22 | 23 | return $user; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/SetUserPassword.php: -------------------------------------------------------------------------------- 1 | ['required', 'string', 'min:8', 'confirmed'], 19 | ])->validateWithBag('setPassword'); 20 | 21 | $user->forceFill([ 22 | 'password' => Hash::make($input['password']), 23 | ])->save(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/UpdateCompanyName.php: -------------------------------------------------------------------------------- 1 | $input 18 | * 19 | * @throws AuthorizationException 20 | */ 21 | public function update(User $user, Company $company, array $input): void 22 | { 23 | Gate::forUser($user)->authorize('update', $company); 24 | 25 | Validator::make($input, [ 26 | 'name' => ['required', 'string', 'max:255'], 27 | ])->validateWithBag('updateCompanyName'); 28 | 29 | $company->forceFill([ 30 | 'name' => $input['name'], 31 | ])->save(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/UpdateConnectedAccount.php: -------------------------------------------------------------------------------- 1 | authorize('update', $connectedAccount); 21 | 22 | $connectedAccount->forceFill([ 23 | 'provider' => strtolower($provider), 24 | 'provider_id' => $providerUser->getId(), 25 | 'name' => $providerUser->getName(), 26 | 'nickname' => $providerUser->getNickname(), 27 | 'email' => $providerUser->getEmail(), 28 | 'avatar_path' => $providerUser->getAvatar(), 29 | 'token' => $providerUser->token, 30 | 'secret' => $providerUser->tokenSecret ?? null, 31 | 'refresh_token' => $providerUser->refreshToken ?? null, 32 | 'expires_at' => property_exists($providerUser, 'expiresIn') ? now()->addSeconds($providerUser->expiresIn) : null, 33 | ])->save(); 34 | 35 | return $connectedAccount; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/UpdateUserPassword.php: -------------------------------------------------------------------------------- 1 | $input 16 | */ 17 | public function update(User $user, array $input): void 18 | { 19 | Validator::make($input, [ 20 | 'current_password' => ['required', 'string', 'current_password:web'], 21 | 'password' => ['required', 'string', 'min:8', 'confirmed'], 22 | ], [ 23 | 'current_password.current_password' => __('filament-companies::default.errors.password_does_not_match'), 24 | ])->validateWithBag('updatePassword'); 25 | 26 | $user->forceFill([ 27 | 'password' => Hash::make($input['password']), 28 | ])->save(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /stubs/app/Actions/FilamentCompanies/UpdateUserProfileInformation.php: -------------------------------------------------------------------------------- 1 | $input 17 | */ 18 | public function update(User $user, array $input): void 19 | { 20 | Validator::make($input, [ 21 | 'name' => ['required', 'string', 'max:255'], 22 | 'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], 23 | 'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'], 24 | ])->validateWithBag('updateProfileInformation'); 25 | 26 | if (isset($input['photo'])) { 27 | $user->updateProfilePhoto($input['photo']); 28 | } 29 | 30 | if ($input['email'] !== $user->email && 31 | $this->userMustVerifyEmail()) { 32 | $this->updateVerifiedUser($user, $input); 33 | } else { 34 | $user->forceFill([ 35 | 'name' => $input['name'], 36 | 'email' => $input['email'], 37 | ])->save(); 38 | } 39 | } 40 | 41 | /** 42 | * Determine if the user must verify their email address. 43 | */ 44 | protected function userMustVerifyEmail(): bool 45 | { 46 | return in_array(MustVerifyEmail::class, class_implements(User::class)); 47 | } 48 | 49 | /** 50 | * Update the given verified user's profile information. 51 | * 52 | * @param array $input 53 | */ 54 | protected function updateVerifiedUser(User $user, array $input): void 55 | { 56 | $user->forceFill([ 57 | 'name' => $input['name'], 58 | 'email' => $input['email'], 59 | 'email_verified_at' => null, 60 | ])->save(); 61 | 62 | $user->sendEmailVerificationNotification(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /stubs/app/Models/Company.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | protected $fillable = [ 22 | 'name', 23 | 'personal_company', 24 | ]; 25 | 26 | /** 27 | * The event map for the model. 28 | * 29 | * @var array 30 | */ 31 | protected $dispatchesEvents = [ 32 | 'created' => CompanyCreated::class, 33 | 'updated' => CompanyUpdated::class, 34 | 'deleted' => CompanyDeleted::class, 35 | ]; 36 | 37 | /** 38 | * Get the attributes that should be cast. 39 | * 40 | * @return array 41 | */ 42 | protected function casts(): array 43 | { 44 | return [ 45 | 'personal_company' => 'boolean', 46 | ]; 47 | } 48 | 49 | public function getFilamentAvatarUrl(): string 50 | { 51 | return $this->owner->profile_photo_url; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /stubs/app/Models/CompanyInvitation.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | protected $fillable = [ 17 | 'email', 18 | 'role', 19 | ]; 20 | 21 | /** 22 | * Get the company that the invitation belongs to. 23 | */ 24 | public function company(): BelongsTo 25 | { 26 | return $this->belongsTo(FilamentCompanies::companyModel()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /stubs/app/Models/ConnectedAccount.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | protected $fillable = [ 21 | 'provider', 22 | 'provider_id', 23 | 'name', 24 | 'nickname', 25 | 'email', 26 | 'avatar_path', 27 | 'token', 28 | 'refresh_token', 29 | 'expires_at', 30 | ]; 31 | 32 | /** 33 | * The event map for the model. 34 | * 35 | * @var array 36 | */ 37 | protected $dispatchesEvents = [ 38 | 'created' => ConnectedAccountCreated::class, 39 | 'updated' => ConnectedAccountUpdated::class, 40 | 'deleted' => ConnectedAccountDeleted::class, 41 | ]; 42 | } 43 | -------------------------------------------------------------------------------- /stubs/app/Models/Employeeship.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | protected $fillable = [ 33 | 'name', 34 | 'email', 35 | 'password', 36 | ]; 37 | 38 | /** 39 | * The attributes that should be hidden for serialization. 40 | * 41 | * @var array 42 | */ 43 | protected $hidden = [ 44 | 'password', 45 | 'remember_token', 46 | ]; 47 | 48 | /** 49 | * The accessors to append to the model's array form. 50 | * 51 | * @var array 52 | */ 53 | protected $appends = [ 54 | 'profile_photo_url', 55 | ]; 56 | 57 | /** 58 | * Get the attributes that should be cast. 59 | * 60 | * @return array 61 | */ 62 | protected function casts(): array 63 | { 64 | return [ 65 | 'email_verified_at' => 'datetime', 66 | 'password' => 'hashed', 67 | ]; 68 | } 69 | 70 | public function canAccessPanel(Panel $panel): bool 71 | { 72 | return true; 73 | } 74 | 75 | public function canAccessTenant(Model $tenant): bool 76 | { 77 | return $this->belongsToCompany($tenant); 78 | } 79 | 80 | public function getTenants(Panel $panel): array | Collection 81 | { 82 | return $this->allCompanies(); 83 | } 84 | 85 | public function getDefaultTenant(Panel $panel): ?Model 86 | { 87 | return $this->currentCompany; 88 | } 89 | 90 | public function getFilamentAvatarUrl(): string 91 | { 92 | return $this->profile_photo_url; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /stubs/app/Models/UserWithSocialite.php: -------------------------------------------------------------------------------- 1 | 35 | */ 36 | protected $fillable = [ 37 | 'name', 38 | 'email', 39 | 'password', 40 | ]; 41 | 42 | /** 43 | * The attributes that should be hidden for serialization. 44 | * 45 | * @var array 46 | */ 47 | protected $hidden = [ 48 | 'password', 49 | 'remember_token', 50 | ]; 51 | 52 | /** 53 | * The accessors to append to the model's array form. 54 | * 55 | * @var array 56 | */ 57 | protected $appends = [ 58 | 'profile_photo_url', 59 | ]; 60 | 61 | /** 62 | * Get the attributes that should be cast. 63 | * 64 | * @return array 65 | */ 66 | protected function casts(): array 67 | { 68 | return [ 69 | 'email_verified_at' => 'datetime', 70 | 'password' => 'hashed', 71 | ]; 72 | } 73 | 74 | public function canAccessPanel(Panel $panel): bool 75 | { 76 | return true; 77 | } 78 | 79 | public function canAccessTenant(Model $tenant): bool 80 | { 81 | return $this->belongsToCompany($tenant); 82 | } 83 | 84 | public function getTenants(Panel $panel): array | Collection 85 | { 86 | return $this->allCompanies(); 87 | } 88 | 89 | public function getDefaultTenant(Panel $panel): ?Model 90 | { 91 | return $this->currentCompany; 92 | } 93 | 94 | public function getFilamentAvatarUrl(): string 95 | { 96 | return $this->profile_photo_url; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /stubs/app/Policies/CompanyPolicy.php: -------------------------------------------------------------------------------- 1 | belongsToCompany($company); 27 | } 28 | 29 | /** 30 | * Determine whether the user can create models. 31 | */ 32 | public function create(User $user): bool 33 | { 34 | return true; 35 | } 36 | 37 | /** 38 | * Determine whether the user can update the model. 39 | */ 40 | public function update(User $user, Company $company): bool 41 | { 42 | return $user->ownsCompany($company); 43 | } 44 | 45 | /** 46 | * Determine whether the user can add company employees. 47 | */ 48 | public function addCompanyEmployee(User $user, Company $company): bool 49 | { 50 | return $user->ownsCompany($company); 51 | } 52 | 53 | /** 54 | * Determine whether the user can update company employee permissions. 55 | */ 56 | public function updateCompanyEmployee(User $user, Company $company): bool 57 | { 58 | return $user->ownsCompany($company); 59 | } 60 | 61 | /** 62 | * Determine whether the user can remove company employees. 63 | */ 64 | public function removeCompanyEmployee(User $user, Company $company): bool 65 | { 66 | return $user->ownsCompany($company); 67 | } 68 | 69 | /** 70 | * Determine whether the user can delete the model. 71 | */ 72 | public function delete(User $user, Company $company): bool 73 | { 74 | return $user->ownsCompany($company); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /stubs/app/Policies/ConnectedAccountPolicy.php: -------------------------------------------------------------------------------- 1 | ownsConnectedAccount($connectedAccount); 27 | } 28 | 29 | /** 30 | * Determine whether the user can create models. 31 | */ 32 | public function create(User $user): bool 33 | { 34 | return true; 35 | } 36 | 37 | /** 38 | * Determine whether the user can update the model. 39 | */ 40 | public function update(User $user, ConnectedAccount $connectedAccount): bool 41 | { 42 | return $user->ownsConnectedAccount($connectedAccount); 43 | } 44 | 45 | /** 46 | * Determine whether the user can delete the model. 47 | */ 48 | public function delete(User $user, ConnectedAccount $connectedAccount): bool 49 | { 50 | return $user->ownsConnectedAccount($connectedAccount); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /stubs/resources/markdown/policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | Edit this file to define the privacy policy for your application. 4 | 5 | ## Introduction 6 | 7 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce ullamcorper non libero nec volutpat. Nullam et nibh ac nisl dictum blandit. Sed id sagittis urna. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus sit amet quam vitae odio sollicitudin volutpat in vel orci. Donec nec dolor vitae dolor feugiat condimentum. 8 | 9 | ## Information Collection and Use 10 | 11 | Mauris non tempus ante, a mollis tellus. Sed vel nulla lorem. Nulla facilisi. Praesent pellentesque, orci nec mollis fermentum, dolor felis venenatis magna, vel scelerisque nisl tellus ac ante. Aliquam erat volutpat. Nulla facilisi. Sed libero purus, gravida in eleifend at, iaculis et est. Mauris pharetra nibh eu libero vehicula, vitae consequat velit blandit. 12 | 13 | ## Log Data 14 | 15 | Cras placerat, velit et lacinia molestie, mi libero laoreet risus, eu cursus ante eros id nisl. Nunc vitae libero lacus. Pellentesque vel sapien sed lorem suscipit commodo. Phasellus eget nisi quis lectus faucibus vehicula. Mauris mollis nunc quis nisi vehicula, sit amet hendrerit velit dapibus. Donec sit amet nulla sed arcu lobortis elementum. 16 | 17 | ## Cookies 18 | 19 | Quisque egestas, arcu et convallis placerat, velit orci dapibus leo, eget vulputate eros nisl vitae risus. Vivamus vel tincidunt libero, sit amet sodales enim. Nulla sagittis, sapien at volutpat fermentum, ipsum arcu pretium quam, eget placerat enim sapien in nibh. Sed ac finibus sapien, at sollicitudin dolor. 20 | 21 | ## Service Providers 22 | 23 | Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. 24 | -------------------------------------------------------------------------------- /stubs/resources/markdown/terms.md: -------------------------------------------------------------------------------- 1 | # Terms of Service 2 | 3 | Edit this file to define the terms of service for your application. 4 | 5 | ## Introduction 6 | 7 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce ullamcorper non libero nec volutpat. Nullam et nibh ac nisl dictum blandit. Sed id sagittis urna. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus sit amet quam vitae odio sollicitudin volutpat in vel orci. Donec nec dolor vitae dolor feugiat condimentum. 8 | 9 | ## Usage Terms 10 | 11 | Mauris non tempus ante, a mollis tellus. Sed vel nulla lorem. Nulla facilisi. Praesent pellentesque, orci nec mollis fermentum, dolor felis venenatis magna, vel scelerisque nisl tellus ac ante. Aliquam erat volutpat. Nulla facilisi. Sed libero purus, gravida in eleifend at, iaculis et est. Mauris pharetra nibh eu libero vehicula, vitae consequat velit blandit. 12 | 13 | ## User Conduct 14 | 15 | Cras placerat, velit et lacinia molestie, mi libero laoreet risus, eu cursus ante eros id nisl. Nunc vitae libero lacus. Pellentesque vel sapien sed lorem suscipit commodo. Phasellus eget nisi quis lectus faucibus vehicula. Mauris mollis nunc quis nisi vehicula, sit amet hendrerit velit dapibus. Donec sit amet nulla sed arcu lobortis elementum. 16 | 17 | ## Content Ownership 18 | 19 | Quisque egestas, arcu et convallis placerat, velit orci dapibus leo, eget vulputate eros nisl vitae risus. Vivamus vel tincidunt libero, sit amet sodales enim. Nulla sagittis, sapien at volutpat fermentum, ipsum arcu pretium quam, eget placerat enim sapien in nibh. Sed ac finibus sapien, at sollicitudin dolor. 20 | 21 | ## Limitations of Liability 22 | 23 | Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. 24 | --------------------------------------------------------------------------------