Version 0.7.0 #1

Merged
kor-elf merged 90 commits from develop into main 2023-12-08 21:18:23 +06:00
17 changed files with 278 additions and 8 deletions
Showing only changes of commit 907bac5586 - Show all commits

View File

@ -4,6 +4,10 @@ APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_DEFAULT_USER_TIMEZONE=UTC
# Valid languages: ru | en
APP_DEFAULT_LOCALE=ru
LOG_CHANNEL=daily
LOG_DEPRECATIONS_CHANNEL=deprecations
LOG_LEVEL=debug

View 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
View 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
View 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');
}
}

View File

@ -2,8 +2,11 @@
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\UpdateRequest;
use App\Http\Requests\Private\Profile\UpdateSettingsRequest;
use App\Services\Private\ProfileService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@ -25,7 +28,9 @@ final class ProfileController extends Controller
public function settings(Request $request): View
{
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());
}
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());
}
}

View 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),
);
}
}

View File

@ -3,6 +3,7 @@
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Enums\Lang;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -34,7 +35,7 @@ final class User extends Authenticatable
'password',
'timezone',
'is_active',
'locale',
'lang',
];
/**
@ -54,7 +55,8 @@ final class User extends Authenticatable
*/
protected $casts = [
'email_verified_at' => 'datetime',
'is_active' => 'boolean'
'is_active' => 'boolean',
'lang' => Lang::class,
];
/**

View File

@ -4,6 +4,7 @@ namespace App\Services\Private;
use App\Dto\Request\Private\Profile\Update;
use App\Dto\Request\Private\Profile\UpdatePassword;
use App\Dto\Request\Private\Profile\UpdateSettings;
use App\Models\User;
use App\ServiceResults\ServiceResultError;
use App\ServiceResults\ServiceResultSuccess;
@ -40,4 +41,19 @@ final class ProfileService extends Service
}
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'));
}
}

View 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()
]);
}
}

View File

@ -70,6 +70,7 @@ return [
*/
'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([
// 'ExampleClass' => App\Example\ExampleClass::class,
'Helpers' => \App\Helpers\Helpers::class,
])->toArray(),
];

View File

@ -45,5 +45,7 @@
"Dashboard": "Dashboard",
"Save": "Save",
"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"
}

View File

@ -213,5 +213,7 @@ return [
'updated_at' => 'updated at',
'username' => 'username',
'year' => 'year',
'lang' => 'language',
'timezone' => 'timezone',
],
];

View File

@ -45,5 +45,7 @@
"Dashboard": "Dashboard",
"Save": "Сохранить",
"Profile saved successfully": "Профиль успешно сохранен",
"The password has been changed": "Пароль был изменен"
"The password has been changed": "Пароль был изменен",
"Default": "По умолчанию",
"The settings have been saved": "Настройки были сохранены"
}

View File

@ -213,5 +213,7 @@ return [
'updated_at' => 'обновлено в',
'username' => 'никнейм',
'year' => 'год',
'lang' => 'язык',
'timezone' => 'часовой пояс',
],
];

View 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>

View 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>

View File

@ -17,7 +17,7 @@ Route::middleware('guest')->group(function () {
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(['auth', 'verified'])->group(function () {
Route::middleware(['auth', 'verified', 'user.locale'])->group(function () {
Route::post('logout', [\App\Http\Controllers\AuthController::class, 'logout'])->name('logout');
Route::get('/', [\App\Http\Controllers\Private\DashboardController::class, 'index'])->name('home');
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('password', [\App\Http\Controllers\Private\ProfileController::class, 'updatePassword'])->name('update-password');
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');
});
});