Initial commit
This commit is contained in:
825
app/Http/Livewire/Profiles/Create.php
Normal file
825
app/Http/Livewire/Profiles/Create.php
Normal file
@@ -0,0 +1,825 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Profiles;
|
||||
|
||||
use App\Events\Auth\RegisteredByAdmin;
|
||||
use App\Http\Livewire\Traits\RequiresAdminAuthorization;
|
||||
use App\Models\Account;
|
||||
use App\Models\Admin;
|
||||
use App\Models\Bank;
|
||||
use App\Models\Language;
|
||||
use App\Models\Locations\Country;
|
||||
use App\Models\Locations\Location;
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Component;
|
||||
use WireUi\Traits\WireUiActions;
|
||||
|
||||
class Create extends Component
|
||||
{
|
||||
use WireUiActions, RequiresAdminAuthorization;
|
||||
|
||||
public bool $showCreateModal = false;
|
||||
// #[Validate] // Add this attribute
|
||||
public $createProfile = [
|
||||
'name' => null,
|
||||
'full_name' => null,
|
||||
'email' => null,
|
||||
'password' => null,
|
||||
'password_confirmation' => null,
|
||||
'lang_preference' => null,
|
||||
'type' => null,
|
||||
];
|
||||
public $profileTypeOptions = [];
|
||||
public $profileTypeSelected;
|
||||
public $linkBankOptions = [];
|
||||
public $linkBankSelected;
|
||||
public $linkUserOptions = [];
|
||||
public $linkUserSelected;
|
||||
|
||||
public bool $generateRandomPassword = true;
|
||||
private ?string $generatedPlainTextPassword = null; // Add property to store plain password
|
||||
|
||||
|
||||
public $country;
|
||||
public $division;
|
||||
public $city;
|
||||
public $district;
|
||||
// Keep track of whether validation is needed
|
||||
public $validateCountry = false;
|
||||
public $validateDivision = false;
|
||||
public $validateCity = false;
|
||||
|
||||
public $localeOptions;
|
||||
|
||||
protected $listeners = ['countryToParent', 'divisionToParent', 'cityToParent', 'districtToParent'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
// CRITICAL: Authorize admin access for profile creation component
|
||||
$this->authorizeAdminAccess();
|
||||
}
|
||||
|
||||
protected function rules()
|
||||
{
|
||||
$rules = [
|
||||
'createProfile' => 'array',
|
||||
'createProfile.type' => ['required', 'string'],
|
||||
'country' => 'required_if:validateCountry,true',
|
||||
'division' => 'required_if:validateDivision,true',
|
||||
'city' => 'required_if:validateCity,true',
|
||||
'district' => 'sometimes',
|
||||
];
|
||||
|
||||
// Dynamically add rules based on type
|
||||
if (!empty($this->createProfile['type'])) {
|
||||
$typeKey = 'profile_' . strtolower(class_basename($this->createProfile['type']));
|
||||
$isUserType = $this->createProfile['type'] === \App\Models\User::class;
|
||||
$isOrgType = $this->createProfile['type'] === \App\Models\Organization::class;
|
||||
$isAdminType = $this->createProfile['type'] === \App\Models\Admin::class;
|
||||
$isBankType = $this->createProfile['type'] === \App\Models\Bank::class;
|
||||
|
||||
// --- Add rules for common fields like name, full_name, email ---
|
||||
$rules['createProfile.name'] = Rule::when(
|
||||
fn ($input) => isset($input['createProfile']['name']),
|
||||
timebank_config("rules.{$typeKey}.name", []),
|
||||
[]
|
||||
);
|
||||
$rules['createProfile.full_name'] = Rule::when(
|
||||
fn ($input) => isset($input['createProfile']['full_name']),
|
||||
timebank_config("rules.{$typeKey}.full_name", []),
|
||||
[]
|
||||
);
|
||||
$rules['createProfile.email'] = Rule::when(
|
||||
fn ($input) => isset($input['createProfile']['email']),
|
||||
timebank_config("rules.{$typeKey}.email", []),
|
||||
[]
|
||||
);
|
||||
|
||||
// --- Conditional Password Rules (Only for User type) ---
|
||||
|
||||
//TODO NEXT: fix manual password confirmation
|
||||
|
||||
if ($isUserType) {
|
||||
$rules['createProfile.password'] = Rule::when(
|
||||
!$this->generateRandomPassword,
|
||||
// Explicitly add 'confirmed' here for the final validation
|
||||
// Merge with rules from config, ensuring 'confirmed' is present
|
||||
timebank_config("rules.{$typeKey}.password" // Get base rules
|
||||
),
|
||||
['nullable', 'string'] // Rules when generating random password
|
||||
);
|
||||
$rules['createProfile.lang_preference'] = ['string', 'max:3'];
|
||||
} else {
|
||||
$rules['createProfile.password'] = ['nullable', 'string'];
|
||||
$rules['createProfile.password_confirmation'] = ['nullable', 'string'];
|
||||
}
|
||||
|
||||
|
||||
// --- Conditional Link Rules ---
|
||||
// Link Bank is required for User and Organization
|
||||
if ($isUserType || $isOrgType) {
|
||||
$rules['createProfile.linkBank'] = ['required', 'integer'];
|
||||
} else {
|
||||
// Ensure it's not required if not rendered
|
||||
$rules['createProfile.linkBank'] = ['nullable', 'integer'];
|
||||
}
|
||||
|
||||
// Link User is required for Organization, Admin, and Bank
|
||||
if ($isOrgType || $isAdminType || $isBankType) {
|
||||
$rules['createProfile.linkUser'] = ['required', 'integer'];
|
||||
} else {
|
||||
// Ensure it's not required if not rendered
|
||||
$rules['createProfile.linkUser'] = ['nullable', 'integer'];
|
||||
}
|
||||
} else {
|
||||
// Default rules if type is not yet selected (optional, but good practice)
|
||||
$rules['createProfile.linkBank'] = ['nullable', 'integer'];
|
||||
$rules['createProfile.linkUser'] = ['nullable', 'integer'];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
|
||||
public function openCreateModal()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
$this->showCreateModal = true;
|
||||
|
||||
$appLocale = app()->getLocale();
|
||||
|
||||
// Get all optional profiles from config
|
||||
$this->profileTypeOptions = collect(timebank_config('profiles'))
|
||||
->map(function ($data, $key) {
|
||||
return [
|
||||
'name' => ucfirst($key),
|
||||
'value' => 'App\Models\\'. ucfirst($key),
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
$this->generateRandomPassword = true; // Ensure it's checked on open
|
||||
$this->generateAndSetPassword(); // Generate initial password
|
||||
|
||||
$this->localeOptions = Language::all()->filter(function ($lang) {
|
||||
return ($lang->lang_code);
|
||||
})->map(function ($lang) {
|
||||
return [
|
||||
'lang_code' => $lang->lang_code,
|
||||
'label' => $lang->flag . ' ' . trans('messages.' . $lang->name),
|
||||
];
|
||||
})->toArray();
|
||||
|
||||
|
||||
$this->resetErrorBag(['country', 'division', 'city', 'district']); // Clear location errors
|
||||
}
|
||||
|
||||
|
||||
public function updatedCreateProfileType()
|
||||
{
|
||||
$selectedType = $this->createProfile['type'] ?? null;
|
||||
$optionsCollection = collect(); // Initialize empty collection
|
||||
|
||||
if ($selectedType === \App\Models\User::class || $selectedType === \App\Models\Organization::class) {
|
||||
// Banks higher than level 1 are non-system banks
|
||||
$optionsBankCollection = Bank::where('level', '>=', 2)->get(['id', 'name', 'full_name', 'email', 'profile_photo_path']);
|
||||
// Make email visible if it's hidden and needed for description
|
||||
$optionsBankCollection->each(fn ($item) => $item->makeVisible('email'));
|
||||
// Same procedure for User model
|
||||
$optionsUserCollection = User::get(['id', 'name', 'full_name', 'email', 'profile_photo_path']);
|
||||
$optionsUserCollection->each(fn ($item) => $item->makeVisible('email'));
|
||||
|
||||
|
||||
// Map to a plain array structure, needed for the wireUi user-option template
|
||||
$this->linkBankOptions = $optionsBankCollection->map(function ($item) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'name' => $item->name,
|
||||
'email' => $item->email,
|
||||
'profile_photo_url' => $item->profile_photo_url,
|
||||
];
|
||||
})->toArray();
|
||||
$this->linkUserOptions = $optionsUserCollection->map(function ($item) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'name' => $item->name,
|
||||
'email' => $item->email,
|
||||
'profile_photo_url' => $item->profile_photo_url,
|
||||
];
|
||||
})->toArray();
|
||||
|
||||
|
||||
} elseif ($selectedType) {
|
||||
$optionsUserCollection = User::get(['id', 'name', 'full_name', 'email', 'profile_photo_path']);
|
||||
$optionsUserCollection->each(fn ($item) => $item->makeVisible('email'));
|
||||
|
||||
$this->linkUserOptions = $optionsUserCollection->map(function ($item) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'name' => $item->name,
|
||||
'email' => $item->email,
|
||||
'profile_photo_url' => $item->profile_photo_url,
|
||||
];
|
||||
})->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function updated($propertyName)
|
||||
{
|
||||
// Only validate createProfile.* fields when they themselves change
|
||||
if (str_starts_with($propertyName, 'createProfile.')
|
||||
&& $propertyName !== 'createProfile.type') {
|
||||
$this->validateOnly($propertyName);
|
||||
}
|
||||
|
||||
// If the 'type' field specifically was updated, handle that separately
|
||||
if ($propertyName === 'createProfile.type') {
|
||||
$this->resetErrorBag(['createProfile.name', 'createProfile.full_name']);
|
||||
$this->validateOnly('createProfile.name');
|
||||
$this->validateOnly('createProfile.full_name');
|
||||
$this->updatedCreateProfileType();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Method called when checkbox state changes
|
||||
public function updatedGenerateRandomPassword(bool $value)
|
||||
{
|
||||
if ($value) {
|
||||
// Checkbox is CHECKED - Generate password
|
||||
$this->generateAndSetPassword();
|
||||
} else {
|
||||
// Checkbox is UNCHECKED - Clear password fields for manual input
|
||||
$this->createProfile['password'] = null;
|
||||
$this->createProfile['password_confirmation'] = null;
|
||||
// Reset validation errors for password fields
|
||||
$this->resetErrorBag(['createProfile.password', 'createProfile.password_confirmation']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Helper function to generate and set password
|
||||
private function generateAndSetPassword()
|
||||
{
|
||||
$password = Str::password(12, true, true, true, false);
|
||||
$this->generatedPlainTextPassword = $password; // Store plain text
|
||||
$this->createProfile['password'] = $password; // Set for validation/hashing
|
||||
$this->createProfile['passwordConfirmation'] = null;
|
||||
$this->resetErrorBag(['createProfile.password', 'createProfile.password_confirmation']);
|
||||
}
|
||||
|
||||
|
||||
public function emitLocationToChildren()
|
||||
{
|
||||
$this->dispatch('countryToChildren', $this->country);
|
||||
$this->dispatch('divisionToChildren', $this->division);
|
||||
$this->dispatch('cityToChildren', $this->city);
|
||||
$this->dispatch('districtToChildren', $this->district);
|
||||
}
|
||||
|
||||
// --- Listener methods ---
|
||||
// When a location value changes (from child), update the property,
|
||||
// recalculate validation requirements, and trigger validation for that specific field.
|
||||
public function countryToParent($value)
|
||||
{
|
||||
$this->country = $value;
|
||||
|
||||
if ($value) {
|
||||
// Look up language preference by country, if available
|
||||
$countryLanguage = DB::table('country_languages')->where('country_id', $this->country)->pluck('code');
|
||||
count($countryLanguage) === 1 ? $this->createProfile['lang_preference'] = $countryLanguage->first() : $this->createProfile['lang_preference'] = null;
|
||||
}
|
||||
|
||||
$this->setLocationValidationOptions();
|
||||
$this->validateOnly('country'); // Validate country immediately
|
||||
// Also re-validate division/city as their requirement might change
|
||||
$this->validateOnly('division');
|
||||
$this->validateOnly('city');
|
||||
}
|
||||
|
||||
public function divisionToParent($value)
|
||||
{
|
||||
$this->division = $value;
|
||||
$this->setLocationValidationOptions(); // Recalculate requirements
|
||||
$this->validateOnly('division'); // Validate division immediately
|
||||
}
|
||||
|
||||
public function cityToParent($value)
|
||||
{
|
||||
$this->city = $value;
|
||||
$this->setLocationValidationOptions(); // Recalculate requirements
|
||||
$this->validateOnly('city'); // Validate city immediately
|
||||
}
|
||||
|
||||
// District doesn't usually affect others, just validate itself
|
||||
public function districtToParent($value)
|
||||
{
|
||||
$this->district = $value;
|
||||
$this->validateOnly('district');
|
||||
}
|
||||
// --- End Listener methods ---
|
||||
|
||||
|
||||
public function setLocationValidationOptions()
|
||||
{
|
||||
// Store previous state to check if requirements changed
|
||||
$oldValidateDivision = $this->validateDivision;
|
||||
$oldValidateCity = $this->validateCity;
|
||||
|
||||
// Default to true, then adjust based on country data
|
||||
$this->validateCountry = true; // Country is always potentially required initially
|
||||
$this->validateDivision = true;
|
||||
$this->validateCity = true;
|
||||
|
||||
if ($this->country) {
|
||||
$countryModel = Country::find($this->country);
|
||||
if ($countryModel) {
|
||||
$countDivisions = $countryModel->divisions()->count();
|
||||
$countCities = $countryModel->cities()->count();
|
||||
|
||||
// Logic based on available sub-locations for the selected country
|
||||
if ($countDivisions > 0 && $countCities < 1) {
|
||||
$this->validateDivision = true;
|
||||
$this->validateCity = false; // City not needed if none exist for country
|
||||
} elseif ($countDivisions < 1 && $countCities > 0) {
|
||||
$this->validateDivision = false; // Division not needed if none exist
|
||||
$this->validateCity = true;
|
||||
} elseif ($countDivisions < 1 && $countCities < 1) {
|
||||
$this->validateDivision = false; // Neither needed if none exist
|
||||
$this->validateCity = false;
|
||||
} elseif ($countDivisions > 0 && $countCities > 0) {
|
||||
// Prefer City over Division if both exist
|
||||
$this->validateDivision = false; // Assuming city is the primary choice here
|
||||
$this->validateCity = true;
|
||||
|
||||
}
|
||||
} else {
|
||||
// Invalid country selected, potentially keep validation? Or reset?
|
||||
// For now, keep defaults (true) as the country rule itself will fail.
|
||||
}
|
||||
} else {
|
||||
// No country selected, only country is required.
|
||||
$this->validateCountry = true;
|
||||
$this->validateDivision = false;
|
||||
$this->validateCity = false;
|
||||
}
|
||||
|
||||
// --- Re-validate if requirements changed ---
|
||||
// If the requirement for division/city changed, re-trigger their validation
|
||||
// This helps clear errors if they become non-required.
|
||||
if ($this->validateDivision !== $oldValidateDivision) {
|
||||
$this->validateOnly('division');
|
||||
}
|
||||
if ($this->validateCity !== $oldValidateCity) {
|
||||
$this->validateOnly('city');
|
||||
}
|
||||
// --- End Re-validation ---
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles the save button of the create profile modal.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
// CRITICAL: Authorize admin access for creating profiles
|
||||
$this->authorizeAdminAccess();
|
||||
|
||||
// --- If a user, bank, admin profile will be created, determine the plain password that will be emailed ---
|
||||
if ($this->createProfile['type'] === \App\Models\User::class
|
||||
|| $this->createProfile['type'] === \App\Models\Bank::class
|
||||
|| $this->createProfile['type'] === \App\Models\Admin::class ) {
|
||||
if ($this->generateRandomPassword) {
|
||||
$this->generateAndSetPassword();
|
||||
} elseif (!empty($this->createProfile['password'])) {
|
||||
// Capture manually entered password (after trimming)
|
||||
$this->generatedPlainTextPassword = trim($this->createProfile['password']);
|
||||
} else {
|
||||
// Manual mode, but password field is empty
|
||||
$this->generatedPlainTextPassword = null;
|
||||
}
|
||||
} else {
|
||||
// Not a profile type with password, ensure plain password is null
|
||||
$this->generatedPlainTextPassword = null;
|
||||
// Also nullify password fields before validation if not User
|
||||
$this->createProfile['password'] = null;
|
||||
$this->createProfile['passwordConfirmation'] = null;
|
||||
}
|
||||
|
||||
|
||||
// Trim password fields if they exist (important for validation)
|
||||
if (isset($this->createProfile['password'])) {
|
||||
$this->createProfile['password'] = trim($this->createProfile['password']);
|
||||
}
|
||||
if (isset($this->createProfile['passwordConfirmation'])) {
|
||||
$this->createProfile['passwordConfirmation'] = trim($this->createProfile['passwordConfirmation']);
|
||||
}
|
||||
|
||||
// Validate all fields based on current rules
|
||||
$validatedData = $this->validate();
|
||||
$profileData = $validatedData['createProfile']; // Get the nested profile data
|
||||
|
||||
// Remove password confirmation if it exists
|
||||
unset($profileData['password_confirmation']);
|
||||
|
||||
// Add location data to the profile data array for helper methods
|
||||
$profileData['country_id'] = $validatedData['country'] ?? null;
|
||||
$profileData['division_id'] = $validatedData['division'] ?? null;
|
||||
$profileData['city_id'] = $validatedData['city'] ?? null;
|
||||
$profileData['district_id'] = $validatedData['district'] ?? null;
|
||||
|
||||
$newProfile = null;
|
||||
|
||||
try {
|
||||
// Use a transaction for creating the new profile and related models
|
||||
DB::transaction(function () use ($profileData, &$newProfile) {
|
||||
|
||||
switch ($profileData['type']) {
|
||||
case \App\Models\User::class:
|
||||
$newProfile = $this->createUserProfile($profileData);
|
||||
break;
|
||||
case \App\Models\Organization::class:
|
||||
$newProfile = $this->createOrganizationProfile($profileData);
|
||||
break;
|
||||
case \App\Models\Bank::class:
|
||||
$newProfile = $this->createBankProfile($profileData);
|
||||
break;
|
||||
case \App\Models\Admin::class:
|
||||
$newProfile = $this->createAdminProfile($profileData);
|
||||
break;
|
||||
default:
|
||||
throw new \Exception("Unknown profile type: " . $profileData['type']);
|
||||
}
|
||||
|
||||
// Common logic after profile creation (if any) can go here
|
||||
// e.g., creating a default location if not handled in helpers
|
||||
if ($newProfile && !$newProfile->locations()->exists()) {
|
||||
$this->createDefaultLocation($newProfile, $profileData);
|
||||
}
|
||||
|
||||
}); // End of transaction
|
||||
|
||||
|
||||
if ($newProfile) {
|
||||
// Dispatch RegisteredByAdmin event to send email confirmation / password / welcome
|
||||
event(new RegisteredByAdmin($newProfile, $this->generatedPlainTextPassword));
|
||||
}
|
||||
|
||||
|
||||
// Success
|
||||
$this->notification()->success(
|
||||
__('Profile Created'),
|
||||
__('The profile has been successfully created.')
|
||||
);
|
||||
|
||||
$this->showCreateModal = false;
|
||||
$this->dispatch('profileCreated');
|
||||
$this->resetForm();
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// --- Failure ---
|
||||
Log::error('Profile creation failed: ' . $e->getMessage(), [
|
||||
'profile_data' => $profileData, // Log data for debugging
|
||||
'exception' => $e
|
||||
]);
|
||||
|
||||
$this->notification()->error(
|
||||
__('Error'),
|
||||
// Provide a generic error message to the user
|
||||
__('Failed to create profile. Please check the details and try again. If the problem persists, contact support.')
|
||||
);
|
||||
// Keep the modal open for correction
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function createUserProfile(array $data): User
|
||||
{
|
||||
// Hash password
|
||||
$data['password'] = Hash::make($data['password']);
|
||||
|
||||
// Add default values from config
|
||||
$data['profile_photo_path'] = timebank_config('profiles.user.profile_photo_path_new');
|
||||
$data['limit_min'] = timebank_config('profiles.user.limit_min');
|
||||
$data['limit_max'] = timebank_config('profiles.user.limit_max');
|
||||
$data['lang_preference'] = $data['lang_preference'] ?? null;
|
||||
|
||||
|
||||
// Create the User
|
||||
$profile = User::create($data);
|
||||
|
||||
// Attach to Bank
|
||||
if (!empty($data['linkBank'])) {
|
||||
$profile->attachBankClient($data['linkBank']);
|
||||
}
|
||||
|
||||
// Create Account
|
||||
$this->createDefaultAccount($profile, 'user');
|
||||
|
||||
// Create Location
|
||||
$this->createDefaultLocation($profile, $data);
|
||||
|
||||
|
||||
// TODO: Replace commented rtippin messenger logic with wirechat logic
|
||||
// Attach to Messenger
|
||||
// Messenger::getProviderMessenger($profile);
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function createOrganizationProfile(array $data): Organization
|
||||
{
|
||||
// Add default values from config
|
||||
$data['profile_photo_path'] = timebank_config('profiles.organization.profile_photo_path_new');
|
||||
$data['limit_min'] = timebank_config('profiles.organization.limit_min');
|
||||
$data['limit_max'] = timebank_config('profiles.organization.limit_max');
|
||||
$data['lang_preference'] = $data['lang_preference'] ?? null;
|
||||
|
||||
// Create the profile
|
||||
$profile = Organization::create($data);
|
||||
|
||||
// Attach to Bank
|
||||
if (!empty($data['linkBank'])) {
|
||||
$profile->attachBankClient($data['linkBank']);
|
||||
}
|
||||
|
||||
// Attach to profile manager
|
||||
if (!empty($data['linkUser'])) {
|
||||
$profile->managers()->attach($data['linkUser']);
|
||||
|
||||
// Send email notifications to linked user about the new organization
|
||||
$linkedUser = User::find($data['linkUser']);
|
||||
if ($linkedUser) {
|
||||
$this->sendProfileLinkNotifications($profile, $linkedUser);
|
||||
}
|
||||
}
|
||||
|
||||
// Create Account
|
||||
$this->createDefaultAccount($profile, 'organization');
|
||||
|
||||
// Create Location
|
||||
$this->createDefaultLocation($profile, $data);
|
||||
|
||||
|
||||
// TODO: Replace commented rtippin messenger logic with wirechat logic
|
||||
// Attach to Messenger
|
||||
// Messenger::getProviderMessenger($profile);
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
|
||||
private function createBankProfile(array $data): Bank
|
||||
{
|
||||
// Hash password
|
||||
$data['password'] = Hash::make($data['password']);
|
||||
|
||||
// Add default values from config
|
||||
$data['profile_photo_path'] = timebank_config('profiles.bank.profile_photo_path_new');
|
||||
$data['level'] = $data['level'] ?? timebank_config('profiles.bank.level', 1);
|
||||
$data['limit_min'] = timebank_config('profiles.bank.limit_min');
|
||||
$data['limit_max'] = timebank_config('profiles.bank.limit_max');
|
||||
$data['lang_preference'] = $data['lang_preference'] ?? null;
|
||||
|
||||
// Create the profile
|
||||
$profile = Bank::create($data);
|
||||
|
||||
// Attach to profile manager
|
||||
if (!empty($data['linkUser'])) {
|
||||
$profile->managers()->attach($data['linkUser']);
|
||||
|
||||
// Send email notifications to linked user about the new bank
|
||||
$linkedUser = User::find($data['linkUser']);
|
||||
if ($linkedUser) {
|
||||
$this->sendProfileLinkNotifications($profile, $linkedUser);
|
||||
}
|
||||
}
|
||||
// Create Account
|
||||
$this->createDefaultAccount($profile, 'bank');
|
||||
|
||||
// Create debit account for level 0 (source) banks
|
||||
if ($profile->level === 0) {
|
||||
$this->createDefaultAccount($profile, 'debit');
|
||||
}
|
||||
|
||||
// Create Location
|
||||
$this->createDefaultLocation($profile, $data);
|
||||
|
||||
|
||||
// TODO: Replace commented rtippin messenger logic with wirechat logic
|
||||
// Attach to Messenger
|
||||
// Messenger::getProviderMessenger($profile);
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
|
||||
private function createAdminProfile(array $data): Admin
|
||||
{
|
||||
// Hash password
|
||||
$data['password'] = Hash::make($data['password']);
|
||||
|
||||
// Add default values from config
|
||||
$data['profile_photo_path'] = timebank_config('profiles.admin.profile_photo_path_new');
|
||||
$data['limit_min'] = timebank_config('profiles.admin.limit_min');
|
||||
$data['limit_max'] = timebank_config('profiles.admin.limit_max');
|
||||
$data['lang_preference'] = $data['lang_preference'] ?? null;
|
||||
|
||||
// Create the profile
|
||||
$profile = Admin::create($data);
|
||||
|
||||
// Attach to User
|
||||
if (!empty($data['linkUser'])) {
|
||||
$profile->users()->attach($data['linkUser']);
|
||||
$linkedUser = User::find($data['linkUser']);
|
||||
$linkedUser->assignRole('admin');
|
||||
|
||||
// Create and assign scoped Admin role (required by getCanManageProfiles())
|
||||
$scopedRoleName = "Admin\\{$profile->id}\\admin";
|
||||
$scopedRole = \Spatie\Permission\Models\Role::findOrCreate($scopedRoleName, 'web');
|
||||
$scopedRole->syncPermissions(\Spatie\Permission\Models\Permission::all());
|
||||
$linkedUser->assignRole($scopedRoleName);
|
||||
|
||||
// Send email notifications to linked user about the new admin profile
|
||||
$this->sendProfileLinkNotifications($profile, $linkedUser);
|
||||
}
|
||||
|
||||
// Create Location
|
||||
$this->createDefaultLocation($profile, $data);
|
||||
|
||||
|
||||
// TODO: Replace commented rtippin messenger logic with wirechat logic
|
||||
// Attach to Messenger
|
||||
// Messenger::getProviderMessenger($profile);
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Helper function to create Default Location ---
|
||||
private function createDefaultLocation($profileModel, array $data): void
|
||||
{
|
||||
if (empty($data['country_id'])) {
|
||||
return;
|
||||
} // Don't create if no country
|
||||
|
||||
$location = new Location();
|
||||
$location->name = __('Default location');
|
||||
$location->country_id = $data['country_id'];
|
||||
$location->division_id = $data['division_id'] ?? null;
|
||||
$location->city_id = $data['city_id'] ?? null;
|
||||
$location->district_id = $data['district_id'] ?? null;
|
||||
$profileModel->locations()->save($location);
|
||||
}
|
||||
|
||||
// --- Helper function to create Default Account ---
|
||||
private function createDefaultAccount($profileModel, string $type): void
|
||||
{
|
||||
// Check if accounts are enabled for this type and config exists
|
||||
$accountConfig = timebank_config("accounts.{$type}");
|
||||
if (!$accountConfig) {
|
||||
Log::info("Account creation skipped for type '{$type}': No config found.");
|
||||
return;
|
||||
}
|
||||
|
||||
$account = new Account();
|
||||
$account->name = __(timebank_config("accounts.{$type}.name", 'default Account'));
|
||||
$account->limit_min = timebank_config("accounts.{$type}.limit_min", 0);
|
||||
$account->limit_max = timebank_config("accounts.{$type}.limit_max", 0);
|
||||
|
||||
// Associate account with the profile model (assuming polymorphic relation 'accounts')
|
||||
$profileModel->accounts()->save($account);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resets the form fields to their initial state.
|
||||
*/
|
||||
public function resetForm()
|
||||
{
|
||||
$this->showCreateModal = false;
|
||||
|
||||
// Reset the main profile data array
|
||||
$this->createProfile = [
|
||||
'name' => null,
|
||||
'full_name' => null,
|
||||
'email' => null,
|
||||
'password' => null,
|
||||
'password_confirmation' => null,
|
||||
'phone' => null,
|
||||
'comment' => null,
|
||||
'lang_preference' => null,
|
||||
'type' => 'user', // Reset type back to default
|
||||
'linkBank' => null, // Add linkBank if it's part of the array
|
||||
'linkUser' => null, // Add linkUser if it's part of the array
|
||||
];
|
||||
|
||||
// Reset select options and selections
|
||||
$this->profileTypeOptions = [];
|
||||
$this->profileTypeSelected = null;
|
||||
$this->linkBankOptions = [];
|
||||
$this->linkBankSelected = null;
|
||||
$this->linkUserOptions = [];
|
||||
$this->linkUserSelected = null;
|
||||
|
||||
// Reset password generation flag
|
||||
$this->generateRandomPassword = true;
|
||||
$this->generatedPlainTextPassword = null; // Clear stored password
|
||||
|
||||
// Reset location properties
|
||||
$this->country = null;
|
||||
$this->division = null;
|
||||
$this->city = null;
|
||||
$this->district = null;
|
||||
|
||||
// Reset location validation flags
|
||||
$this->validateCountry = true;
|
||||
$this->validateDivision = true;
|
||||
$this->validateCity = true;
|
||||
|
||||
// Clear validation errors
|
||||
$this->resetErrorBag();
|
||||
|
||||
// Re-fetch initial options if needed when resetting
|
||||
$this->updatedCreateProfileType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send profile link notification emails to both the profile and the linked user
|
||||
*
|
||||
* @param mixed $profile The newly created profile (Organization/Bank/Admin)
|
||||
* @param User $linkedUser The user being linked to the profile
|
||||
* @return void
|
||||
*/
|
||||
private function sendProfileLinkNotifications($profile, User $linkedUser): void
|
||||
{
|
||||
Log::info('ProfileLinkCreated: Sending email notifications', [
|
||||
'profile_id' => $profile->id,
|
||||
'profile_type' => get_class($profile),
|
||||
'profile_name' => $profile->name,
|
||||
'linked_user_id' => $linkedUser->id,
|
||||
'linked_user_email' => $linkedUser->email ?? 'NO EMAIL',
|
||||
]);
|
||||
|
||||
// Send email to the linked user
|
||||
$userMessageSetting = \App\Models\MessageSetting::where('message_settingable_id', $linkedUser->id)
|
||||
->where('message_settingable_type', get_class($linkedUser))
|
||||
->first();
|
||||
|
||||
$sendUserEmail = $userMessageSetting ? $userMessageSetting->system_message : true;
|
||||
|
||||
Log::info('ProfileLinkCreated: Linked user message setting check', [
|
||||
'has_message_setting' => $userMessageSetting ? 'yes' : 'no',
|
||||
'system_message' => $userMessageSetting ? $userMessageSetting->system_message : 'default:true',
|
||||
'will_send_email' => $sendUserEmail ? 'yes' : 'no',
|
||||
]);
|
||||
|
||||
if ($sendUserEmail) {
|
||||
\App\Jobs\SendProfileLinkChangedMail::dispatch($linkedUser, $profile, 'attached');
|
||||
Log::info('ProfileLinkCreated: Dispatched email to linked user', [
|
||||
'recipient_email' => $linkedUser->email,
|
||||
'profile_name' => $profile->name,
|
||||
]);
|
||||
} else {
|
||||
Log::info('ProfileLinkCreated: Skipped email to linked user (system_message disabled)');
|
||||
}
|
||||
|
||||
// Send email to the newly created profile
|
||||
$profileMessageSetting = \App\Models\MessageSetting::where('message_settingable_id', $profile->id)
|
||||
->where('message_settingable_type', get_class($profile))
|
||||
->first();
|
||||
|
||||
$sendProfileEmail = $profileMessageSetting ? $profileMessageSetting->system_message : true;
|
||||
|
||||
Log::info('ProfileLinkCreated: Profile message setting check', [
|
||||
'has_message_setting' => $profileMessageSetting ? 'yes' : 'no',
|
||||
'system_message' => $profileMessageSetting ? $profileMessageSetting->system_message : 'default:true',
|
||||
'will_send_email' => $sendProfileEmail ? 'yes' : 'no',
|
||||
]);
|
||||
|
||||
if ($sendProfileEmail) {
|
||||
\App\Jobs\SendProfileLinkChangedMail::dispatch($profile, $linkedUser, 'attached');
|
||||
Log::info('ProfileLinkCreated: Dispatched email to profile', [
|
||||
'recipient_email' => $profile->email,
|
||||
'linked_user_name' => $linkedUser->name,
|
||||
]);
|
||||
} else {
|
||||
Log::info('ProfileLinkCreated: Skipped email to profile (system_message disabled)');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
2154
app/Http/Livewire/Profiles/Manage.php
Normal file
2154
app/Http/Livewire/Profiles/Manage.php
Normal file
File diff suppressed because it is too large
Load Diff
40
app/Http/Livewire/Profiles/ProfileTypesDropdown.php
Normal file
40
app/Http/Livewire/Profiles/ProfileTypesDropdown.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Profiles;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class ProfileTypesDropdown extends Component
|
||||
{
|
||||
public $selectedTypes = [];
|
||||
public $hideLabel = false;
|
||||
|
||||
protected $listeners = ['profileTypesToChildren'];
|
||||
|
||||
public function profileTypesToChildren($value)
|
||||
{
|
||||
$this->selectedTypes = $value;
|
||||
}
|
||||
|
||||
public function updatedSelectedTypes()
|
||||
{
|
||||
$this->dispatch('profileTypesToParent', $this->selectedTypes);
|
||||
}
|
||||
|
||||
public function profileTypesSelected()
|
||||
{
|
||||
$this->dispatch('profileTypesToParent', $this->selectedTypes);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$profileTypes = [
|
||||
'User' => __('User'),
|
||||
'Organization' => __('Organization'),
|
||||
'Bank' => __('Bank'),
|
||||
'Admin' => __('Admin')
|
||||
];
|
||||
|
||||
return view('livewire.profiles.profile-types-dropdown', compact('profileTypes'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user