Version 0.7.0 #1
@ -4,6 +4,10 @@ APP_KEY=
|
|||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
APP_DEFAULT_USER_TIMEZONE=UTC
|
||||||
|
# Valid languages: ru | en
|
||||||
|
APP_DEFAULT_LOCALE=ru
|
||||||
|
|
||||||
LOG_CHANNEL=daily
|
LOG_CHANNEL=daily
|
||||||
LOG_DEPRECATIONS_CHANNEL=deprecations
|
LOG_DEPRECATIONS_CHANNEL=deprecations
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
24
app/Dto/Request/Private/Profile/UpdateSettings.php
Normal file
24
app/Dto/Request/Private/Profile/UpdateSettings.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Request\Private\Profile;
|
||||||
|
|
||||||
|
use App\Dto\Request\Dto;
|
||||||
|
use App\Enums\Lang;
|
||||||
|
|
||||||
|
final readonly class UpdateSettings extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ?Lang $lang,
|
||||||
|
private ?string $timezone
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getLang(): ?Lang
|
||||||
|
{
|
||||||
|
return $this->lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTimezone(): ?string
|
||||||
|
{
|
||||||
|
return $this->timezone;
|
||||||
|
}
|
||||||
|
}
|
46
app/Enums/Lang.php
Normal file
46
app/Enums/Lang.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
enum Lang: int
|
||||||
|
{
|
||||||
|
case Ru = 1;
|
||||||
|
case En = 2;
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Ru => 'Русский',
|
||||||
|
self::En => 'English'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLocale(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Ru => 'ru',
|
||||||
|
self::En => 'en'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function toArray(): array
|
||||||
|
{
|
||||||
|
$choices = [];
|
||||||
|
foreach (self::cases() as $lang) {
|
||||||
|
$choices[] = [
|
||||||
|
'name' => $lang->name,
|
||||||
|
'value' => $lang->value,
|
||||||
|
'title' => $lang->getTitle(),
|
||||||
|
'locale' => $lang->getLocale()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function toCollection(): Collection
|
||||||
|
{
|
||||||
|
return collect(self::toArray());
|
||||||
|
}
|
||||||
|
}
|
24
app/Helpers/Helpers.php
Normal file
24
app/Helpers/Helpers.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
final readonly class Helpers
|
||||||
|
{
|
||||||
|
public static function getTimeZoneList(): Collection
|
||||||
|
{
|
||||||
|
return Cache::rememberForever('timezones_list_collection', function () {
|
||||||
|
$timezone = [];
|
||||||
|
foreach (timezone_identifiers_list(\DateTimeZone::ALL) as $key => $value) {
|
||||||
|
$timezone[$value] = $value . ' (UTC ' . now($value)->format('P') . ')';
|
||||||
|
}
|
||||||
|
return collect($timezone)->sortKeys();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getUserTimeZone() {
|
||||||
|
return auth()->user()?->timezone ?? config('app.user_timezone');
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Private;
|
namespace App\Http\Controllers\Private;
|
||||||
|
|
||||||
|
use App\Enums\Lang;
|
||||||
|
use App\Helpers\Helpers;
|
||||||
use App\Http\Requests\Private\Profile\UpdatePasswordRequest;
|
use App\Http\Requests\Private\Profile\UpdatePasswordRequest;
|
||||||
use App\Http\Requests\Private\Profile\UpdateRequest;
|
use App\Http\Requests\Private\Profile\UpdateRequest;
|
||||||
|
use App\Http\Requests\Private\Profile\UpdateSettingsRequest;
|
||||||
use App\Services\Private\ProfileService;
|
use App\Services\Private\ProfileService;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -25,7 +28,9 @@ final class ProfileController extends Controller
|
|||||||
public function settings(Request $request): View
|
public function settings(Request $request): View
|
||||||
{
|
{
|
||||||
return view('private/profile/settings', [
|
return view('private/profile/settings', [
|
||||||
'user' => $request->user()
|
'user' => $request->user(),
|
||||||
|
'languages' => Lang::toCollection()->pluck(value: 'title', key: 'value')->toArray(),
|
||||||
|
'timezone' => Helpers::getTimeZoneList()->toArray(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,4 +57,16 @@ final class ProfileController extends Controller
|
|||||||
}
|
}
|
||||||
return redirect()->route('profile.edit')->withSuccess($result->getMessage());
|
return redirect()->route('profile.edit')->withSuccess($result->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateSettings(UpdateSettingsRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
$result = $this->profileService->updateSettings($data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getMessage());
|
||||||
|
}
|
||||||
|
return redirect()->route('profile.settings')->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
38
app/Http/Requests/Private/Profile/UpdateSettingsRequest.php
Normal file
38
app/Http/Requests/Private/Profile/UpdateSettingsRequest.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Private\Profile;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Request\Private\Profile\UpdateSettings;
|
||||||
|
use App\Enums\Lang;
|
||||||
|
use App\Helpers\Helpers;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\Rules\Enum;
|
||||||
|
|
||||||
|
final class UpdateSettingsRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'lang' => ['nullable', new Enum(Lang::class)],
|
||||||
|
'timezone' => ['nullable', Rule::in(Helpers::getTimeZoneList()->keys()->toArray())]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): UpdateSettings
|
||||||
|
{
|
||||||
|
$lang = $this->input('lang', null);
|
||||||
|
if (!is_null($lang)) {
|
||||||
|
$lang = Lang::from((int) $lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UpdateSettings(
|
||||||
|
lang: $lang,
|
||||||
|
timezone: $this->input('timezone', null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use App\Enums\Lang;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
@ -34,7 +35,7 @@ final class User extends Authenticatable
|
|||||||
'password',
|
'password',
|
||||||
'timezone',
|
'timezone',
|
||||||
'is_active',
|
'is_active',
|
||||||
'locale',
|
'lang',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,7 +55,8 @@ final class User extends Authenticatable
|
|||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'is_active' => 'boolean'
|
'is_active' => 'boolean',
|
||||||
|
'lang' => Lang::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,6 +4,7 @@ namespace App\Services\Private;
|
|||||||
|
|
||||||
use App\Dto\Request\Private\Profile\Update;
|
use App\Dto\Request\Private\Profile\Update;
|
||||||
use App\Dto\Request\Private\Profile\UpdatePassword;
|
use App\Dto\Request\Private\Profile\UpdatePassword;
|
||||||
|
use App\Dto\Request\Private\Profile\UpdateSettings;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\ServiceResults\ServiceResultError;
|
use App\ServiceResults\ServiceResultError;
|
||||||
use App\ServiceResults\ServiceResultSuccess;
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
@ -40,4 +41,19 @@ final class ProfileService extends Service
|
|||||||
}
|
}
|
||||||
return $this->ok(__('The password has been changed'));
|
return $this->ok(__('The password has been changed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateSettings(UpdateSettings $update, User $user): ServiceResultError | ServiceResultSuccess
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = [
|
||||||
|
'lang' => $update->getLang(),
|
||||||
|
'timezone' => $update->getTimezone(),
|
||||||
|
];
|
||||||
|
$this->userCommandHandler->handleUpdate($user, $data);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e->getMessage());
|
||||||
|
return $this->errService($e->getMessage());
|
||||||
|
}
|
||||||
|
return $this->ok(__('The settings have been saved'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
54
app/View/Components/Private/Forms/Select.php
Normal file
54
app/View/Components/Private/Forms/Select.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\View\Components\Private\Forms;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class Select extends Form
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $title
|
||||||
|
* @param string $name
|
||||||
|
* @param array $list = [ [key => value], ... ]
|
||||||
|
* @param null|string $value
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $title,
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly array $list,
|
||||||
|
private readonly ?string $value = ''
|
||||||
|
) { }
|
||||||
|
|
||||||
|
protected function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTitle(): string
|
||||||
|
{
|
||||||
|
return Str::ucfirst($this->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getValue(): string
|
||||||
|
{
|
||||||
|
return (string) old($this->getRequestName(), $this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getList(): array
|
||||||
|
{
|
||||||
|
return $this->list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('private.components.forms.select', [
|
||||||
|
'title' => $this->getTitle(),
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'requestName' => $this->getRequestName(),
|
||||||
|
'list' => $this->getList(),
|
||||||
|
'value' => $this->getValue()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -70,6 +70,7 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'timezone' => 'UTC',
|
'timezone' => 'UTC',
|
||||||
|
'user_timezone' => env('APP_DEFAULT_USER_TIMEZONE', 'UTC'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -82,7 +83,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'locale' => 'ru',
|
'locale' => env('APP_DEFAULT_LOCALE', 'ru'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -209,7 +210,7 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'aliases' => Facade::defaultAliases()->merge([
|
'aliases' => Facade::defaultAliases()->merge([
|
||||||
// 'ExampleClass' => App\Example\ExampleClass::class,
|
'Helpers' => \App\Helpers\Helpers::class,
|
||||||
])->toArray(),
|
])->toArray(),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@ -45,5 +45,7 @@
|
|||||||
"Dashboard": "Dashboard",
|
"Dashboard": "Dashboard",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Profile saved successfully": "Profile saved successfully",
|
"Profile saved successfully": "Profile saved successfully",
|
||||||
"The password has been changed": "The password has been changed"
|
"The password has been changed": "The password has been changed",
|
||||||
|
"Default": "default",
|
||||||
|
"The settings have been saved": "The settings have been saved"
|
||||||
}
|
}
|
||||||
|
@ -213,5 +213,7 @@ return [
|
|||||||
'updated_at' => 'updated at',
|
'updated_at' => 'updated at',
|
||||||
'username' => 'username',
|
'username' => 'username',
|
||||||
'year' => 'year',
|
'year' => 'year',
|
||||||
|
'lang' => 'language',
|
||||||
|
'timezone' => 'timezone',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -45,5 +45,7 @@
|
|||||||
"Dashboard": "Dashboard",
|
"Dashboard": "Dashboard",
|
||||||
"Save": "Сохранить",
|
"Save": "Сохранить",
|
||||||
"Profile saved successfully": "Профиль успешно сохранен",
|
"Profile saved successfully": "Профиль успешно сохранен",
|
||||||
"The password has been changed": "Пароль был изменен"
|
"The password has been changed": "Пароль был изменен",
|
||||||
|
"Default": "По умолчанию",
|
||||||
|
"The settings have been saved": "Настройки были сохранены"
|
||||||
}
|
}
|
||||||
|
@ -213,5 +213,7 @@ return [
|
|||||||
'updated_at' => 'обновлено в',
|
'updated_at' => 'обновлено в',
|
||||||
'username' => 'никнейм',
|
'username' => 'никнейм',
|
||||||
'year' => 'год',
|
'year' => 'год',
|
||||||
|
'lang' => 'язык',
|
||||||
|
'timezone' => 'часовой пояс',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
12
resources/views/private/components/forms/select.blade.php
Normal file
12
resources/views/private/components/forms/select.blade.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<div class="mb-4">
|
||||||
|
<label for="form-select-{{ $requestName }}">{{ $title }}</label>
|
||||||
|
<select id="form-select-{{ $requestName }}" aria-label="{{ $title }}" class="form-select @error($requestName) is-invalid @enderror" name="{{ $name }}" {{ $attributes }}>
|
||||||
|
{{ $slot }}
|
||||||
|
@foreach($list as $elementKey => $elementValue)
|
||||||
|
<option value="{{ $elementKey }}" @selected((string) $elementKey === (string) $value)>{{ $elementValue }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@error($name)
|
||||||
|
<span class="invalid-feedback">{{ $message }}</span>
|
||||||
|
@enderror
|
||||||
|
</div>
|
23
resources/views/private/profile/settings.blade.php
Normal file
23
resources/views/private/profile/settings.blade.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
@section('meta_title', __('Settings'))
|
||||||
|
@section('h1', __('Settings'))
|
||||||
|
<x-private.layout>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow components-section">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" action="{{ route('profile.update-settings') }}">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
<x-private.forms.select :title="__('validation.attributes.lang')" name="lang" :list="$languages" :value="$user->lang?->value">
|
||||||
|
<option value="">{{ __('Default') }}</option>
|
||||||
|
</x-private.forms.select>
|
||||||
|
<x-private.forms.select :title="__('validation.attributes.timezone')" name="timezone" :list="$timezone" :value="$user->timezone">
|
||||||
|
<option value="">{{ __('Default') }}</option>
|
||||||
|
</x-private.forms.select>
|
||||||
|
<button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-private.layout>
|
@ -17,7 +17,7 @@ Route::middleware('guest')->group(function () {
|
|||||||
Route::get('login', [\App\Http\Controllers\AuthController::class, 'login'])->name('login');
|
Route::get('login', [\App\Http\Controllers\AuthController::class, 'login'])->name('login');
|
||||||
Route::middleware(['throttle:login'])->post('login', [\App\Http\Controllers\AuthController::class, 'authorization'])->name('authorization');
|
Route::middleware(['throttle:login'])->post('login', [\App\Http\Controllers\AuthController::class, 'authorization'])->name('authorization');
|
||||||
});
|
});
|
||||||
Route::middleware(['auth', 'verified'])->group(function () {
|
Route::middleware(['auth', 'verified', 'user.locale'])->group(function () {
|
||||||
Route::post('logout', [\App\Http\Controllers\AuthController::class, 'logout'])->name('logout');
|
Route::post('logout', [\App\Http\Controllers\AuthController::class, 'logout'])->name('logout');
|
||||||
Route::get('/', [\App\Http\Controllers\Private\DashboardController::class, 'index'])->name('home');
|
Route::get('/', [\App\Http\Controllers\Private\DashboardController::class, 'index'])->name('home');
|
||||||
Route::prefix('profile')->as('profile.')
|
Route::prefix('profile')->as('profile.')
|
||||||
@ -26,5 +26,6 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
Route::put('/', [\App\Http\Controllers\Private\ProfileController::class, 'update'])->name('update');
|
Route::put('/', [\App\Http\Controllers\Private\ProfileController::class, 'update'])->name('update');
|
||||||
Route::put('password', [\App\Http\Controllers\Private\ProfileController::class, 'updatePassword'])->name('update-password');
|
Route::put('password', [\App\Http\Controllers\Private\ProfileController::class, 'updatePassword'])->name('update-password');
|
||||||
Route::get('settings', [\App\Http\Controllers\Private\ProfileController::class, 'settings'])->name('settings');
|
Route::get('settings', [\App\Http\Controllers\Private\ProfileController::class, 'settings'])->name('settings');
|
||||||
|
Route::put('settings', [\App\Http\Controllers\Private\ProfileController::class, 'updateSettings'])->name('update-settings');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user