🎉 First commit
This commit is contained in:
33
app/Actions/Fortify/CreateNewUser.php
Normal file
33
app/Actions/Fortify/CreateNewUser.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Concerns\PasswordValidationRules;
|
||||
use App\Concerns\ProfileValidationRules;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Fortify\Contracts\CreatesNewUsers;
|
||||
|
||||
class CreateNewUser implements CreatesNewUsers
|
||||
{
|
||||
use PasswordValidationRules, ProfileValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and create a newly registered user.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function create(array $input): User
|
||||
{
|
||||
Validator::make($input, [
|
||||
...$this->profileRules(),
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
return User::create([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'password' => $input['password'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
29
app/Actions/Fortify/ResetUserPassword.php
Normal file
29
app/Actions/Fortify/ResetUserPassword.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Concerns\PasswordValidationRules;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
||||
|
||||
class ResetUserPassword implements ResetsUserPasswords
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and reset the user's forgotten password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function reset(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
$user->forceFill([
|
||||
'password' => $input['password'],
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
28
app/Concerns/PasswordValidationRules.php
Normal file
28
app/Concerns/PasswordValidationRules.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Concerns;
|
||||
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
trait PasswordValidationRules
|
||||
{
|
||||
/**
|
||||
* Get the validation rules used to validate passwords.
|
||||
*
|
||||
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
|
||||
*/
|
||||
protected function passwordRules(): array
|
||||
{
|
||||
return ['required', 'string', Password::default(), 'confirmed'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules used to validate the current password.
|
||||
*
|
||||
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
|
||||
*/
|
||||
protected function currentPasswordRules(): array
|
||||
{
|
||||
return ['required', 'string', 'current_password'];
|
||||
}
|
||||
}
|
||||
50
app/Concerns/ProfileValidationRules.php
Normal file
50
app/Concerns/ProfileValidationRules.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Concerns;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
trait ProfileValidationRules
|
||||
{
|
||||
/**
|
||||
* Get the validation rules used to validate user profiles.
|
||||
*
|
||||
* @return array<string, array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>>
|
||||
*/
|
||||
protected function profileRules(?int $userId = null): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->nameRules(),
|
||||
'email' => $this->emailRules($userId),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules used to validate user names.
|
||||
*
|
||||
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
|
||||
*/
|
||||
protected function nameRules(): array
|
||||
{
|
||||
return ['required', 'string', 'max:255'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules used to validate user emails.
|
||||
*
|
||||
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
|
||||
*/
|
||||
protected function emailRules(?int $userId = null): array
|
||||
{
|
||||
return [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
$userId === null
|
||||
? Rule::unique(User::class)
|
||||
: Rule::unique(User::class)->ignore($userId),
|
||||
];
|
||||
}
|
||||
}
|
||||
76
app/Filament/Pages/Articles.php
Normal file
76
app/Filament/Pages/Articles.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Services\LogisticsService;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
class Articles extends Page
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedCube;
|
||||
|
||||
protected static ?string $navigationLabel = 'Articles';
|
||||
|
||||
protected static ?string $title = 'Articles';
|
||||
|
||||
protected static ?int $navigationSort = 2;
|
||||
|
||||
protected string $view = 'filament.pages.articles';
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public string $select = 'artid,artname';
|
||||
|
||||
public int $results = 10;
|
||||
|
||||
public string $stockArticleId = '';
|
||||
|
||||
public array $data = [];
|
||||
|
||||
public array $stockData = [];
|
||||
|
||||
public ?array $metadata = null;
|
||||
|
||||
public ?string $errorMessage = null;
|
||||
|
||||
public function searchArticles(): void
|
||||
{
|
||||
try {
|
||||
$service = app(LogisticsService::class);
|
||||
|
||||
$params = array_filter([
|
||||
'select' => $this->select,
|
||||
'results' => $this->results,
|
||||
'search' => $this->search,
|
||||
]);
|
||||
|
||||
$response = $service->artList($params);
|
||||
|
||||
$this->data = $response['data'] ?? [];
|
||||
$this->metadata = $response['metadata'] ?? null;
|
||||
$this->errorMessage = $response['error'] ?? null;
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessage = $e->getMessage();
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
||||
|
||||
public function getStock(): void
|
||||
{
|
||||
if (blank($this->stockArticleId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$service = app(LogisticsService::class);
|
||||
$response = $service->artGetStock($this->stockArticleId);
|
||||
|
||||
$this->stockData = $response['data'] ?? [];
|
||||
$this->errorMessage = $response['error'] ?? null;
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessage = $e->getMessage();
|
||||
$this->stockData = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
75
app/Filament/Pages/Documents.php
Normal file
75
app/Filament/Pages/Documents.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Services\LogisticsService;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
class Documents extends Page
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedDocumentText;
|
||||
|
||||
protected static ?string $navigationLabel = 'Documents';
|
||||
|
||||
protected static ?string $title = 'Documents';
|
||||
|
||||
protected static ?int $navigationSort = 3;
|
||||
|
||||
protected string $view = 'filament.pages.documents';
|
||||
|
||||
public string $select = 'jnl,number,thirdid,date';
|
||||
|
||||
public string $thirdId = '';
|
||||
|
||||
public string $detailJnl = '';
|
||||
|
||||
public string $detailNumber = '';
|
||||
|
||||
public array $data = [];
|
||||
|
||||
public array $detailData = [];
|
||||
|
||||
public ?array $metadata = null;
|
||||
|
||||
public ?string $errorMessage = null;
|
||||
|
||||
public function searchDocuments(): void
|
||||
{
|
||||
try {
|
||||
$service = app(LogisticsService::class);
|
||||
|
||||
$params = array_filter([
|
||||
'select' => $this->select,
|
||||
'thirdid' => $this->thirdId,
|
||||
]);
|
||||
|
||||
$response = $service->documentList($params);
|
||||
|
||||
$this->data = $response['data'] ?? [];
|
||||
$this->metadata = $response['metadata'] ?? null;
|
||||
$this->errorMessage = $response['error'] ?? null;
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessage = $e->getMessage();
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
||||
|
||||
public function getDocumentDetail(): void
|
||||
{
|
||||
if (blank($this->detailJnl) || blank($this->detailNumber)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$service = app(LogisticsService::class);
|
||||
$response = $service->documentDetail($this->detailJnl, $this->detailNumber);
|
||||
|
||||
$this->detailData = $response['data'] ?? [];
|
||||
$this->errorMessage = $response['error'] ?? null;
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessage = $e->getMessage();
|
||||
$this->detailData = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
54
app/Filament/Pages/Journaux.php
Normal file
54
app/Filament/Pages/Journaux.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Services\LogisticsService;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
class Journaux extends Page
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedBookOpen;
|
||||
|
||||
protected static ?string $navigationLabel = 'Journaux';
|
||||
|
||||
protected static ?string $title = 'Journaux';
|
||||
|
||||
protected static ?int $navigationSort = 4;
|
||||
|
||||
protected string $view = 'filament.pages.journaux';
|
||||
|
||||
public string $select = '';
|
||||
|
||||
public int $results = 10;
|
||||
|
||||
public string $type = '';
|
||||
|
||||
public array $data = [];
|
||||
|
||||
public ?array $metadata = null;
|
||||
|
||||
public ?string $errorMessage = null;
|
||||
|
||||
public function searchJournaux(): void
|
||||
{
|
||||
try {
|
||||
$service = app(LogisticsService::class);
|
||||
|
||||
$params = array_filter([
|
||||
'select' => $this->select,
|
||||
'results' => $this->results,
|
||||
'TYPE' => $this->type,
|
||||
]);
|
||||
|
||||
$response = $service->jnlList($params);
|
||||
|
||||
$this->data = $response['data'] ?? [];
|
||||
$this->metadata = $response['metadata'] ?? null;
|
||||
$this->errorMessage = $response['error'] ?? null;
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessage = $e->getMessage();
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
67
app/Filament/Pages/TablesExplorer.php
Normal file
67
app/Filament/Pages/TablesExplorer.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Services\LogisticsService;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Livewire\Attributes\Url;
|
||||
|
||||
class TablesExplorer extends Page
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedTableCells;
|
||||
|
||||
protected static ?string $navigationLabel = 'Tables';
|
||||
|
||||
protected static ?string $title = 'Explorateur de tables';
|
||||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
protected string $view = 'filament.pages.tables-explorer';
|
||||
|
||||
#[Url]
|
||||
public string $selectedTable = '';
|
||||
|
||||
public array $tables = [];
|
||||
|
||||
public array $columns = [];
|
||||
|
||||
public ?string $errorMessage = null;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->loadTables();
|
||||
}
|
||||
|
||||
public function loadTables(): void
|
||||
{
|
||||
try {
|
||||
$service = app(LogisticsService::class);
|
||||
$response = $service->tablesList();
|
||||
|
||||
$this->tables = $response['data'] ?? [];
|
||||
$this->errorMessage = $response['error'] ?? null;
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessage = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function loadColumns(): void
|
||||
{
|
||||
if (blank($this->selectedTable)) {
|
||||
$this->columns = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$service = app(LogisticsService::class);
|
||||
$response = $service->columnList($this->selectedTable);
|
||||
|
||||
$this->columns = $response['data'] ?? [];
|
||||
$this->errorMessage = $response['error'] ?? null;
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessage = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
76
app/Filament/Pages/Tiers.php
Normal file
76
app/Filament/Pages/Tiers.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Services\LogisticsService;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
class Tiers extends Page
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedUsers;
|
||||
|
||||
protected static ?string $navigationLabel = 'Tiers';
|
||||
|
||||
protected static ?string $title = 'Tiers';
|
||||
|
||||
protected static ?int $navigationSort = 5;
|
||||
|
||||
protected string $view = 'filament.pages.tiers';
|
||||
|
||||
public string $select = 'custid,custname';
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public int $results = 10;
|
||||
|
||||
public string $historyThirdId = '';
|
||||
|
||||
public array $data = [];
|
||||
|
||||
public array $historyData = [];
|
||||
|
||||
public ?array $metadata = null;
|
||||
|
||||
public ?string $errorMessage = null;
|
||||
|
||||
public function searchTiers(): void
|
||||
{
|
||||
try {
|
||||
$service = app(LogisticsService::class);
|
||||
|
||||
$params = array_filter([
|
||||
'select' => $this->select,
|
||||
'results' => $this->results,
|
||||
'search' => $this->search,
|
||||
]);
|
||||
|
||||
$response = $service->thirdList($params);
|
||||
|
||||
$this->data = $response['data'] ?? [];
|
||||
$this->metadata = $response['metadata'] ?? null;
|
||||
$this->errorMessage = $response['error'] ?? null;
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessage = $e->getMessage();
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
||||
|
||||
public function getArtHistory(): void
|
||||
{
|
||||
if (blank($this->historyThirdId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$service = app(LogisticsService::class);
|
||||
$response = $service->thirdGetArtHistory($this->historyThirdId);
|
||||
|
||||
$this->historyData = $response['data'] ?? [];
|
||||
$this->errorMessage = $response['error'] ?? null;
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessage = $e->getMessage();
|
||||
$this->historyData = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
22
app/Livewire/Actions/Logout.php
Normal file
22
app/Livewire/Actions/Logout.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Actions;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class Logout
|
||||
{
|
||||
/**
|
||||
* Log the current user out of the application.
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
Session::invalidate();
|
||||
Session::regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
10
app/Livewire/Settings/Appearance.php
Normal file
10
app/Livewire/Settings/Appearance.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class Appearance extends Component
|
||||
{
|
||||
//
|
||||
}
|
||||
29
app/Livewire/Settings/DeleteUserForm.php
Normal file
29
app/Livewire/Settings/DeleteUserForm.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use App\Concerns\PasswordValidationRules;
|
||||
use App\Livewire\Actions\Logout;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Component;
|
||||
|
||||
class DeleteUserForm extends Component
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
public string $password = '';
|
||||
|
||||
/**
|
||||
* Delete the currently authenticated user.
|
||||
*/
|
||||
public function deleteUser(Logout $logout): void
|
||||
{
|
||||
$this->validate([
|
||||
'password' => $this->currentPasswordRules(),
|
||||
]);
|
||||
|
||||
tap(Auth::user(), $logout(...))->delete();
|
||||
|
||||
$this->redirect('/', navigate: true);
|
||||
}
|
||||
}
|
||||
44
app/Livewire/Settings/Password.php
Normal file
44
app/Livewire/Settings/Password.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use App\Concerns\PasswordValidationRules;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Livewire\Component;
|
||||
|
||||
class Password extends Component
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
public string $current_password = '';
|
||||
|
||||
public string $password = '';
|
||||
|
||||
public string $password_confirmation = '';
|
||||
|
||||
/**
|
||||
* Update the password for the currently authenticated user.
|
||||
*/
|
||||
public function updatePassword(): void
|
||||
{
|
||||
try {
|
||||
$validated = $this->validate([
|
||||
'current_password' => $this->currentPasswordRules(),
|
||||
'password' => $this->passwordRules(),
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
$this->reset('current_password', 'password', 'password_confirmation');
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
Auth::user()->update([
|
||||
'password' => $validated['password'],
|
||||
]);
|
||||
|
||||
$this->reset('current_password', 'password', 'password_confirmation');
|
||||
|
||||
$this->dispatch('password-updated');
|
||||
}
|
||||
}
|
||||
79
app/Livewire/Settings/Profile.php
Normal file
79
app/Livewire/Settings/Profile.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use App\Concerns\ProfileValidationRules;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Component;
|
||||
|
||||
class Profile extends Component
|
||||
{
|
||||
use ProfileValidationRules;
|
||||
|
||||
public string $name = '';
|
||||
|
||||
public string $email = '';
|
||||
|
||||
/**
|
||||
* Mount the component.
|
||||
*/
|
||||
public function mount(): void
|
||||
{
|
||||
$this->name = Auth::user()->name;
|
||||
$this->email = Auth::user()->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the profile information for the currently authenticated user.
|
||||
*/
|
||||
public function updateProfileInformation(): void
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$validated = $this->validate($this->profileRules($user->id));
|
||||
|
||||
$user->fill($validated);
|
||||
|
||||
if ($user->isDirty('email')) {
|
||||
$user->email_verified_at = null;
|
||||
}
|
||||
|
||||
$user->save();
|
||||
|
||||
$this->dispatch('profile-updated', name: $user->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email verification notification to the current user.
|
||||
*/
|
||||
public function resendVerificationNotification(): void
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($user->hasVerifiedEmail()) {
|
||||
$this->redirectIntended(default: route('dashboard', absolute: false));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
|
||||
Session::flash('status', 'verification-link-sent');
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function hasUnverifiedEmail(): bool
|
||||
{
|
||||
return Auth::user() instanceof MustVerifyEmail && ! Auth::user()->hasVerifiedEmail();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function showDeleteUser(): bool
|
||||
{
|
||||
return ! Auth::user() instanceof MustVerifyEmail
|
||||
|| (Auth::user() instanceof MustVerifyEmail && Auth::user()->hasVerifiedEmail());
|
||||
}
|
||||
}
|
||||
182
app/Livewire/Settings/TwoFactor.php
Normal file
182
app/Livewire/Settings/TwoFactor.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use Exception;
|
||||
use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication;
|
||||
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
|
||||
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
|
||||
use Laravel\Fortify\Features;
|
||||
use Laravel\Fortify\Fortify;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class TwoFactor extends Component
|
||||
{
|
||||
#[Locked]
|
||||
public bool $twoFactorEnabled;
|
||||
|
||||
#[Locked]
|
||||
public bool $requiresConfirmation;
|
||||
|
||||
#[Locked]
|
||||
public string $qrCodeSvg = '';
|
||||
|
||||
#[Locked]
|
||||
public string $manualSetupKey = '';
|
||||
|
||||
public bool $showModal = false;
|
||||
|
||||
public bool $showVerificationStep = false;
|
||||
|
||||
#[Validate('required|string|size:6', onUpdate: false)]
|
||||
public string $code = '';
|
||||
|
||||
/**
|
||||
* Mount the component.
|
||||
*/
|
||||
public function mount(DisableTwoFactorAuthentication $disableTwoFactorAuthentication): void
|
||||
{
|
||||
abort_unless(Features::enabled(Features::twoFactorAuthentication()), Response::HTTP_FORBIDDEN);
|
||||
|
||||
if (Fortify::confirmsTwoFactorAuthentication() && is_null(auth()->user()->two_factor_confirmed_at)) {
|
||||
$disableTwoFactorAuthentication(auth()->user());
|
||||
}
|
||||
|
||||
$this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication();
|
||||
$this->requiresConfirmation = Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable two-factor authentication for the user.
|
||||
*/
|
||||
public function enable(EnableTwoFactorAuthentication $enableTwoFactorAuthentication): void
|
||||
{
|
||||
$enableTwoFactorAuthentication(auth()->user());
|
||||
|
||||
if (! $this->requiresConfirmation) {
|
||||
$this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication();
|
||||
}
|
||||
|
||||
$this->loadSetupData();
|
||||
|
||||
$this->showModal = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the two-factor authentication setup data for the user.
|
||||
*/
|
||||
private function loadSetupData(): void
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
try {
|
||||
$this->qrCodeSvg = $user?->twoFactorQrCodeSvg();
|
||||
$this->manualSetupKey = decrypt($user->two_factor_secret);
|
||||
} catch (Exception) {
|
||||
$this->addError('setupData', 'Failed to fetch setup data.');
|
||||
|
||||
$this->reset('qrCodeSvg', 'manualSetupKey');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the two-factor verification step if necessary.
|
||||
*/
|
||||
public function showVerificationIfNecessary(): void
|
||||
{
|
||||
if ($this->requiresConfirmation) {
|
||||
$this->showVerificationStep = true;
|
||||
|
||||
$this->resetErrorBag();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closeModal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm two-factor authentication for the user.
|
||||
*/
|
||||
public function confirmTwoFactor(ConfirmTwoFactorAuthentication $confirmTwoFactorAuthentication): void
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$confirmTwoFactorAuthentication(auth()->user(), $this->code);
|
||||
|
||||
$this->closeModal();
|
||||
|
||||
$this->twoFactorEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset two-factor verification state.
|
||||
*/
|
||||
public function resetVerification(): void
|
||||
{
|
||||
$this->reset('code', 'showVerificationStep');
|
||||
|
||||
$this->resetErrorBag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable two-factor authentication for the user.
|
||||
*/
|
||||
public function disable(DisableTwoFactorAuthentication $disableTwoFactorAuthentication): void
|
||||
{
|
||||
$disableTwoFactorAuthentication(auth()->user());
|
||||
|
||||
$this->twoFactorEnabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the two-factor authentication modal.
|
||||
*/
|
||||
public function closeModal(): void
|
||||
{
|
||||
$this->reset(
|
||||
'code',
|
||||
'manualSetupKey',
|
||||
'qrCodeSvg',
|
||||
'showModal',
|
||||
'showVerificationStep',
|
||||
);
|
||||
|
||||
$this->resetErrorBag();
|
||||
|
||||
if (! $this->requiresConfirmation) {
|
||||
$this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current modal configuration state.
|
||||
*/
|
||||
public function getModalConfigProperty(): array
|
||||
{
|
||||
if ($this->twoFactorEnabled) {
|
||||
return [
|
||||
'title' => __('Two-Factor Authentication Enabled'),
|
||||
'description' => __('Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.'),
|
||||
'buttonText' => __('Close'),
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->showVerificationStep) {
|
||||
return [
|
||||
'title' => __('Verify Authentication Code'),
|
||||
'description' => __('Enter the 6-digit code from your authenticator app.'),
|
||||
'buttonText' => __('Continue'),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => __('Enable Two-Factor Authentication'),
|
||||
'description' => __('To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app.'),
|
||||
'buttonText' => __('Continue'),
|
||||
];
|
||||
}
|
||||
}
|
||||
50
app/Livewire/Settings/TwoFactor/RecoveryCodes.php
Normal file
50
app/Livewire/Settings/TwoFactor/RecoveryCodes.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings\TwoFactor;
|
||||
|
||||
use Exception;
|
||||
use Laravel\Fortify\Actions\GenerateNewRecoveryCodes;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Component;
|
||||
|
||||
class RecoveryCodes extends Component
|
||||
{
|
||||
#[Locked]
|
||||
public array $recoveryCodes = [];
|
||||
|
||||
/**
|
||||
* Mount the component.
|
||||
*/
|
||||
public function mount(): void
|
||||
{
|
||||
$this->loadRecoveryCodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate new recovery codes for the user.
|
||||
*/
|
||||
public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generateNewRecoveryCodes): void
|
||||
{
|
||||
$generateNewRecoveryCodes(auth()->user());
|
||||
|
||||
$this->loadRecoveryCodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the recovery codes for the user.
|
||||
*/
|
||||
private function loadRecoveryCodes(): void
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->hasEnabledTwoFactorAuthentication() && $user->two_factor_recovery_codes) {
|
||||
try {
|
||||
$this->recoveryCodes = json_decode(decrypt($user->two_factor_recovery_codes), true);
|
||||
} catch (Exception) {
|
||||
$this->addError('recoveryCodes', 'Failed to load recovery codes');
|
||||
|
||||
$this->recoveryCodes = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
app/Models/User.php
Normal file
64
app/Models/User.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable, TwoFactorAuthenticatable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'two_factor_secret',
|
||||
'two_factor_recovery_codes',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's initials
|
||||
*/
|
||||
public function initials(): string
|
||||
{
|
||||
return Str::of($this->name)
|
||||
->explode(' ')
|
||||
->take(2)
|
||||
->map(fn ($word) => Str::substr($word, 0, 1))
|
||||
->implode('');
|
||||
}
|
||||
}
|
||||
50
app/Providers/AppServiceProvider.php
Normal file
50
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->configureDefaults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure default behaviors for production-ready applications.
|
||||
*/
|
||||
protected function configureDefaults(): void
|
||||
{
|
||||
Date::use(CarbonImmutable::class);
|
||||
|
||||
DB::prohibitDestructiveCommands(
|
||||
app()->isProduction(),
|
||||
);
|
||||
|
||||
Password::defaults(fn (): ?Password => app()->isProduction()
|
||||
? Password::min(12)
|
||||
->mixedCase()
|
||||
->letters()
|
||||
->numbers()
|
||||
->symbols()
|
||||
->uncompromised()
|
||||
: null
|
||||
);
|
||||
}
|
||||
}
|
||||
51
app/Providers/Filament/AdminPanelProvider.php
Normal file
51
app/Providers/Filament/AdminPanelProvider.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Pages\Dashboard;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Widgets\FilamentInfoWidget;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->id('admin')
|
||||
->path('admin')
|
||||
->brandName('API Logistics')
|
||||
->colors([
|
||||
'primary' => Color::Blue,
|
||||
])
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages')
|
||||
->pages([
|
||||
Dashboard::class,
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets')
|
||||
->widgets([
|
||||
FilamentInfoWidget::class,
|
||||
])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
72
app/Providers/FortifyServiceProvider.php
Normal file
72
app/Providers/FortifyServiceProvider.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Actions\Fortify\CreateNewUser;
|
||||
use App\Actions\Fortify\ResetUserPassword;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Fortify\Fortify;
|
||||
|
||||
class FortifyServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->configureActions();
|
||||
$this->configureViews();
|
||||
$this->configureRateLimiting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Fortify actions.
|
||||
*/
|
||||
private function configureActions(): void
|
||||
{
|
||||
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
|
||||
Fortify::createUsersUsing(CreateNewUser::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Fortify views.
|
||||
*/
|
||||
private function configureViews(): void
|
||||
{
|
||||
Fortify::loginView(fn () => view('livewire.auth.login'));
|
||||
Fortify::verifyEmailView(fn () => view('livewire.auth.verify-email'));
|
||||
Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge'));
|
||||
Fortify::confirmPasswordView(fn () => view('livewire.auth.confirm-password'));
|
||||
Fortify::registerView(fn () => view('livewire.auth.register'));
|
||||
Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password'));
|
||||
Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure rate limiting.
|
||||
*/
|
||||
private function configureRateLimiting(): void
|
||||
{
|
||||
RateLimiter::for('two-factor', function (Request $request) {
|
||||
return Limit::perMinute(5)->by($request->session()->get('login.id'));
|
||||
});
|
||||
|
||||
RateLimiter::for('login', function (Request $request) {
|
||||
$throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());
|
||||
|
||||
return Limit::perMinute(5)->by($throttleKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
173
app/Services/LogisticsService.php
Normal file
173
app/Services/LogisticsService.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class LogisticsService
|
||||
{
|
||||
private string $baseUrl;
|
||||
|
||||
private string $apiKey;
|
||||
|
||||
private string $folder;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->baseUrl = rtrim(config('logistics.base_url'), '/');
|
||||
$this->apiKey = config('logistics.api_key');
|
||||
$this->folder = config('logistics.folder');
|
||||
}
|
||||
|
||||
public function tablesList(): array
|
||||
{
|
||||
return $this->post('tables_list');
|
||||
}
|
||||
|
||||
public function columnList(string $table): array
|
||||
{
|
||||
return $this->post("column_list/{$table}");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{select?: string, results?: int, search?: string, barcode?: string} $params
|
||||
*/
|
||||
public function artList(array $params = []): array
|
||||
{
|
||||
return $this->post('art_list', $params);
|
||||
}
|
||||
|
||||
public function artGetStock(string $artId): array
|
||||
{
|
||||
return $this->post('art_getstk', ['ARTID' => $artId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{select?: string, results?: int, TYPE: string} $params
|
||||
*/
|
||||
public function jnlList(array $params = []): array
|
||||
{
|
||||
return $this->post('jnl_list', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{select?: string, thirdid?: string} $params
|
||||
*/
|
||||
public function documentList(array $params = []): array
|
||||
{
|
||||
return $this->post('document_list', $params);
|
||||
}
|
||||
|
||||
public function documentDetail(string $jnl, string $number): array
|
||||
{
|
||||
return $this->post('document_detail', [
|
||||
'jnl' => $jnl,
|
||||
'number' => $number,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{ThirdId: string, Date: string, Artid: array, Qty: array, Saleprice: array, JNL: string, Discount?: array, Vatid?: array, Vatpc?: array, Attachments?: array} $params
|
||||
*/
|
||||
public function documentAdd(array $params): array
|
||||
{
|
||||
return $this->post('document_add', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{number: string, Thirdid?: string, Artid?: array, Qty?: array, Saleprice?: array, JNL: string, Attachments?: array} $params
|
||||
*/
|
||||
public function documentMod(array $params): array
|
||||
{
|
||||
return $this->post('document_mod', $params);
|
||||
}
|
||||
|
||||
public function documentGetStatusList(string $jnl): array
|
||||
{
|
||||
return $this->post('Document_GetStatusList', ['jnl' => $jnl]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{ARTID: string, QTY: string, JNL: string, THIRDID: string, DATE: string} $params
|
||||
*/
|
||||
public function documentGetUnitPriceAndVat(array $params): array
|
||||
{
|
||||
return $this->post('Document_GetUnitPriceAndVat', $params);
|
||||
}
|
||||
|
||||
public function documentGetDueDate(string $payDelay, string $date): array
|
||||
{
|
||||
return $this->post('Document_GetDueDate', [
|
||||
'paydelay' => $payDelay,
|
||||
'date' => $date,
|
||||
]);
|
||||
}
|
||||
|
||||
public function documentGetAttachListThumbnail(string $jnl, string $number): array
|
||||
{
|
||||
return $this->post('Document_GetAttachListThumbnail', [
|
||||
'JNL' => $jnl,
|
||||
'NUMBER' => $number,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{select?: string, results?: int, search: string} $params
|
||||
*/
|
||||
public function thirdList(array $params = []): array
|
||||
{
|
||||
return $this->post('third_list', $params);
|
||||
}
|
||||
|
||||
public function thirdGetArtHistory(string $thirdId): array
|
||||
{
|
||||
return $this->post('third_GetArtHistory', ['thirdid' => $thirdId]);
|
||||
}
|
||||
|
||||
public function getSerialNumber(): array
|
||||
{
|
||||
return $this->post('getserialnumber');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{code: string} $params
|
||||
*/
|
||||
public function codesList(array $params): array
|
||||
{
|
||||
return $this->post('codes_list', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{data: mixed, metadata: array, error: mixed}
|
||||
*/
|
||||
private function post(string $endpoint, array $params = []): array
|
||||
{
|
||||
$url = "{$this->baseUrl}/{$this->folder}/{$endpoint}";
|
||||
|
||||
$response = Http::withHeaders([
|
||||
'X-API-KEY' => $this->apiKey,
|
||||
])->post($url, $params);
|
||||
|
||||
$this->logRequest($endpoint, $params, $response);
|
||||
|
||||
return $response->json() ?? [
|
||||
'data' => null,
|
||||
'metadata' => ['rowcount' => 0, 'issuccess' => false],
|
||||
'error' => 'Empty response from API',
|
||||
];
|
||||
}
|
||||
|
||||
private function logRequest(string $endpoint, array $params, Response $response): void
|
||||
{
|
||||
DB::table('api_request_logs')->insert([
|
||||
'endpoint' => $endpoint,
|
||||
'parameters' => json_encode($params),
|
||||
'response_status' => $response->status(),
|
||||
'response_data' => $response->body(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user