From 8c46816ff83ff11d1cdf01a201beec95f1ecd4ed Mon Sep 17 00:00:00 2001 From: Toy Rik Date: Mon, 23 Feb 2026 12:14:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D0=B5=D0=BB=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/Domain/User/Actions/ShowAction.php | 2 +- src/app/Domain/User/Actions/UpdateAction.php | 24 ++ src/app/Domain/User/Data/ShowResponseData.php | 2 + src/app/Domain/User/Data/UpdateRequest.php | 30 ++- src/app/Helpers/MenuHelper.php | 3 +- src/app/Http/Controllers/UserController.php | 9 + src/lang/ru.json | 16 ++ src/lang/ru/auth.php | 4 + src/lang/ru/pagination.php | 17 ++ src/lang/ru/passwords.php | 22 ++ src/lang/ru/validation.php | 137 +++++++++++ .../components/profile/profile-card.blade.php | 212 ++++++++++++++++++ .../views/components/ui/modal.blade.php | 59 +++++ src/resources/views/pages/user/show.blade.php | 6 +- src/routes/web.php | 3 + 15 files changed, 540 insertions(+), 6 deletions(-) create mode 100644 src/app/Domain/User/Actions/UpdateAction.php create mode 100644 src/lang/ru.json create mode 100644 src/lang/ru/auth.php create mode 100644 src/lang/ru/pagination.php create mode 100644 src/lang/ru/passwords.php create mode 100644 src/lang/ru/validation.php create mode 100644 src/resources/views/components/ui/modal.blade.php diff --git a/src/app/Domain/User/Actions/ShowAction.php b/src/app/Domain/User/Actions/ShowAction.php index 6725c25..71d6a7b 100644 --- a/src/app/Domain/User/Actions/ShowAction.php +++ b/src/app/Domain/User/Actions/ShowAction.php @@ -19,7 +19,7 @@ class ShowAction public function execute(ShowRequest $request) { - $result = $this->userRepository->whereUuid($request->user_uuid)->firstOrFail(); + $result = $this->userRepository->with('role')->whereUuid($request->user_uuid)->firstOrFail(); return ShowResponseData::fromModel($result); } } diff --git a/src/app/Domain/User/Actions/UpdateAction.php b/src/app/Domain/User/Actions/UpdateAction.php new file mode 100644 index 0000000..003b6c3 --- /dev/null +++ b/src/app/Domain/User/Actions/UpdateAction.php @@ -0,0 +1,24 @@ +userRepository->whereUuid($request->user_uuid)->firstOrFail(); + $user->update($request->getFilledFields()); + } +} \ No newline at end of file diff --git a/src/app/Domain/User/Data/ShowResponseData.php b/src/app/Domain/User/Data/ShowResponseData.php index db15ba4..64092af 100644 --- a/src/app/Domain/User/Data/ShowResponseData.php +++ b/src/app/Domain/User/Data/ShowResponseData.php @@ -13,6 +13,7 @@ class ShowResponseData extends Data public function __construct( public readonly string $uuid, public readonly string $name, + public readonly string $email, public readonly RoleData $role, ) { } @@ -22,6 +23,7 @@ class ShowResponseData extends Data return new self( uuid: $model->uuid->toString(), name: $model->name, + email: $model->email, role: new RoleData( uuid: $model->role->uuid->toString(), name: $model->role->name, diff --git a/src/app/Domain/User/Data/UpdateRequest.php b/src/app/Domain/User/Data/UpdateRequest.php index ea20bad..96ae91c 100644 --- a/src/app/Domain/User/Data/UpdateRequest.php +++ b/src/app/Domain/User/Data/UpdateRequest.php @@ -2,13 +2,41 @@ declare(strict_types=1); -use Spatie\LaravelData\Data; +namespace App\Domain\User\Data; +use Spatie\LaravelData\Attributes\FromRouteParameter; +use Spatie\LaravelData\Attributes\MapName; +use Spatie\LaravelData\Attributes\Validation\Email; +use Spatie\LaravelData\Attributes\Validation\Exists; +use Spatie\LaravelData\Attributes\Validation\StringType; +use Spatie\LaravelData\Attributes\Validation\Nullable; +use Spatie\LaravelData\Data; +use Spatie\LaravelData\Mappers\SnakeCaseMapper; + +#[MapName(SnakeCaseMapper::class)] class UpdateRequest extends Data { public function __construct( + #[StringType, FromRouteParameter('user_uuid'), Exists('users', 'uuid')] public readonly string $user_uuid, + #[StringType, Nullable] + public readonly ?string $name, + #[StringType, Email, Nullable] + public readonly ?string $email, + #[StringType, Nullable] + public readonly ?string $password, ) { } + + public function getFilledFields(): array + { + $fields = [ + 'name' => $this->name, + 'email' => $this->email, + 'password' => $this->password ? bcrypt($this->password) : null, + ]; + + return array_filter($fields, fn($value) => $value !== null); + } } diff --git a/src/app/Helpers/MenuHelper.php b/src/app/Helpers/MenuHelper.php index 87004fb..be57b2d 100644 --- a/src/app/Helpers/MenuHelper.php +++ b/src/app/Helpers/MenuHelper.php @@ -49,7 +49,8 @@ class MenuHelper public static function getIconSvg($iconName) { $icons = [ - 'dashboard' => '', + 'dashboard' => 'Dashboard SVG Icon', + 'user-profile' => 'User-avatar SVG Icon' ]; return $icons[$iconName] ?? ''; } diff --git a/src/app/Http/Controllers/UserController.php b/src/app/Http/Controllers/UserController.php index 7c89efe..bde9402 100644 --- a/src/app/Http/Controllers/UserController.php +++ b/src/app/Http/Controllers/UserController.php @@ -5,7 +5,9 @@ declare(strict_types=1); namespace App\Http\Controllers; use App\Domain\User\Actions\ShowAction; +use App\Domain\User\Actions\UpdateAction; use App\Domain\User\Data\ShowRequest; +use App\Domain\User\Data\UpdateRequest; use Illuminate\Contracts\View\View; class UserController extends Controller @@ -20,4 +22,11 @@ class UserController extends Controller $data = $action->execute($request); return view('pages.user.show', ['data' => $data]); } + + public function update(UpdateRequest $request, UpdateAction $action) + { + $data = $action->execute($request); + + return response()->json(['success' => true, 'message' => 'Profile updated successfully']); + } } diff --git a/src/lang/ru.json b/src/lang/ru.json new file mode 100644 index 0000000..96eada4 --- /dev/null +++ b/src/lang/ru.json @@ -0,0 +1,16 @@ +{ + "Phone": "Телефон", + "Log in": "Войти", + "Profile": "Профиль", + "User Profile": "Профиль Пользователя", + "Edit": "Редактировать", + "Personal Information": "Персональная информация", + "Update your details to keep your profile up-to-date.": "Обновите свои данные, чтобы поддерживать актуальность вашего профиля.", + "Name": "Имя", + "Email Address": "Адрес электронной почты", + "Sign Up": "Зарегистрироваться", + "Reset Password": "Сбросить пароль", + "Password": "Пароль", + "Reset": "Сбросить", + "Verification link sent! ": "" +} diff --git a/src/lang/ru/auth.php b/src/lang/ru/auth.php new file mode 100644 index 0000000..72f9043 --- /dev/null +++ b/src/lang/ru/auth.php @@ -0,0 +1,4 @@ + 'Неверный логин или пароль', +]; diff --git a/src/lang/ru/pagination.php b/src/lang/ru/pagination.php new file mode 100644 index 0000000..21c5783 --- /dev/null +++ b/src/lang/ru/pagination.php @@ -0,0 +1,17 @@ + 'Вперёд »', + 'previous' => '« Назад', +]; diff --git a/src/lang/ru/passwords.php b/src/lang/ru/passwords.php new file mode 100644 index 0000000..18e292b --- /dev/null +++ b/src/lang/ru/passwords.php @@ -0,0 +1,22 @@ + 'Ваш пароль был сброшен.', + 'sent' => 'Мы отправили вам по электронной почте ссылку для сброса пароля.', + 'throttled' => 'Пожалуйста, подождите, прежде чем повторить попытку.', + 'token' => 'Этот токен сброса пароля недействителен.', + 'user' => "Пользователь с таким адресом электронной почты не найден.", + +]; diff --git a/src/lang/ru/validation.php b/src/lang/ru/validation.php new file mode 100644 index 0000000..231a124 --- /dev/null +++ b/src/lang/ru/validation.php @@ -0,0 +1,137 @@ + 'Вы должны принять ":attribute".', + 'accepted_if' => 'Вы должны принять ":attribute", когда :other соответствует :value.', + 'active_url' => 'Значение поля ":attribute" не является действительным URL.', + 'after' => 'Значение поля ":attribute" должно быть датой после :date.', + 'after_or_equal' => 'Значение поля ":attribute" должно быть датой после или равной :date.', + 'alpha' => 'Значение поля ":attribute" может содержать только буквы.', + 'alpha_dash' => 'Значение поля ":attribute" может содержать только буквы, цифры, дефис и нижнее подчеркивание.', + 'alpha_num' => 'Значение поля ":attribute" может содержать только буквы и цифры.', + 'array' => 'Значение поля ":attribute" должно быть массивом.', + 'before' => 'Значение поля ":attribute" должно быть датой до :date.', + 'before_or_equal' => 'Значение поля ":attribute" должно быть датой до или равной :date.', + 'between' => [ + 'array' => 'Количество элементов в поле ":attribute" должно быть между :min и :max.', + 'file' => 'Размер файла в поле ":attribute" должен быть между :min и :max Килобайт(а).', + 'numeric' => 'Значение поля ":attribute" должно быть между :min и :max.', + 'string' => 'Количество символов в поле ":attribute" должно быть между :min и :max.', + ], + 'boolean' => 'Значение поля ":attribute" должно быть логического типа.', + 'confirmed' => 'Значение поля ":attribute" не совпадает с подтверждаемым.', + 'current_password' => 'Неверный пароль.', + 'date' => 'Значение поля ":attribute" не является датой.', + 'date_equals' => 'Значение поля ":attribute" должно быть датой равной :date.', + 'date_format' => 'Значение поля ":attribute" не соответствует формату даты :format.', + 'declined' => 'Поле ":attribute" должно быть отклонено.', + 'declined_if' => 'Поле ":attribute" должно быть отклонено, когда :other равно :value.', + 'different' => 'Значения полей ":attribute" и :other должны различаться.', + 'digits' => 'Длина значения цифрового поля ":attribute" должна быть :digits.', + 'digits_between' => 'Длина значения цифрового поля ":attribute" должна быть между :min и :max.', + 'dimensions' => 'Изображение в поле ":attribute" имеет недопустимые размеры.', + 'distinct' => 'Значения поля ":attribute" не должны повторяться.', + 'email' => 'Значение поля ":attribute" должно быть действительным электронным адресом.', + 'ends_with' => 'Поле ":attribute" должно заканчиваться одним из следующих значений: :values', + 'enum' => 'Выбранное значение для ":attribute" некорректно.', + 'exists' => 'Выбранное значение для ":attribute" некорректно.', + 'file' => 'В поле ":attribute" должен быть указан файл.', + 'filled' => 'Поле ":attribute" обязательно для заполнения.', + 'gt' => [ + 'array' => 'Количество элементов в поле ":attribute" должно быть больше :value.', + 'file' => 'Размер файла в поле ":attribute" должен быть больше :value Килобайт(а).', + 'numeric' => 'Значение поля ":attribute" должно быть больше :value.', + 'string' => 'Количество символов в поле ":attribute" должно быть больше :value.', + ], + 'gte' => [ + 'array' => 'Количество элементов в поле ":attribute" должно быть :value или больше.', + 'file' => 'Размер файла в поле ":attribute" должен быть :value Килобайт(а) или больше.', + 'numeric' => 'Значение поля ":attribute" должно быть :value или больше.', + 'string' => 'Количество символов в поле ":attribute" должно быть :value или больше.', + ], + 'image' => 'Файл в поле ":attribute" должен быть изображением.', + 'in' => 'Выбранное значение для ":attribute" некорректно.', + 'in_array' => 'Значение поля ":attribute" не существует в :other.', + 'integer' => 'Значение поля ":attribute" должно быть целым числом.', + 'ip' => 'Значение поля ":attribute" должно быть действительным IP-адресом.', + 'ipv4' => 'Значение поля ":attribute" должно быть действительным IPv4-адресом.', + 'ipv6' => 'Значение поля ":attribute" должно быть действительным IPv6-адресом.', + 'json' => 'Значение поля ":attribute" должно быть JSON строкой.', + 'lt' => [ + 'array' => 'Количество элементов в поле ":attribute" должно быть меньше :value.', + 'file' => 'Размер файла в поле ":attribute" должен быть меньше :value Килобайт(а).', + 'numeric' => 'Значение поля ":attribute" должно быть меньше :value.', + 'string' => 'Количество символов в поле ":attribute" должно быть меньше :value.', + ], + 'lte' => [ + 'array' => 'Количество элементов в поле ":attribute" должно быть :value или меньше.', + 'file' => 'Размер файла в поле ":attribute" должен быть :value Килобайт(а) или меньше.', + 'numeric' => 'Значение поля ":attribute" должно быть :value или меньше.', + 'string' => 'Количество символов в поле ":attribute" должно быть :value или меньше.', + ], + 'mac_address' => 'Значение поля ":attribute" должно быть корректным MAC-адресом.', + 'max' => [ + 'array' => 'Количество элементов в поле ":attribute" не может превышать :max.', + 'file' => 'Размер файла в поле ":attribute" не может быть больше :max Килобайт(а).', + 'numeric' => 'Значение поля ":attribute" не может быть больше :max.', + 'string' => 'Количество символов в поле ":attribute" не может превышать :max.', + ], + 'mimes' => 'Файл в поле ":attribute" должен быть одного из следующих типов: :values.', + 'mimetypes' => 'Файл в поле ":attribute" должен быть одного из следующих типов: :values.', + 'min' => [ + 'array' => 'Количество элементов в поле ":attribute" должно быть не меньше :min.', + 'file' => 'Размер файла в поле ":attribute" должен быть не меньше :min Килобайт(а).', + 'numeric' => 'Значение поля ":attribute" должно быть не меньше :min.', + 'string' => 'Количество символов в поле ":attribute" должно быть не меньше :min.', + ], + 'multiple_of' => 'Значение поля ":attribute" должно быть кратным :value', + 'not_in' => 'Выбранное значение для ":attribute" некорректно.', + 'not_regex' => 'Значение поля ":attribute" некорректно.', + 'numeric' => 'Значение поля ":attribute" должно быть числом.', + 'password' => 'Некорректный пароль.', + 'present' => 'Значение поля ":attribute" должно присутствовать.', + 'prohibited' => 'Значение поля ":attribute" запрещено.', + 'prohibited_if' => 'Значение поля ":attribute" запрещено, когда :other равно :value.', + 'prohibited_unless' => 'Значение поля ":attribute" запрещено, если :other не состоит в :values.', + 'prohibits' => 'Значение поля ":attribute" запрещает присутствие :other.', + 'regex' => 'Значение поля ":attribute" некорректно.', + 'required' => 'Поле ":attribute" обязательно для заполнения.', + 'required_array_keys' => 'Массив в поле ":attribute" обязательно должен иметь ключи: :values', + 'required_if' => 'Поле ":attribute" обязательно для заполнения, когда ":other" равно :value.', + 'required_unless' => 'Поле ":attribute" обязательно для заполнения, когда ":other" не равно :values.', + 'required_with' => 'Поле ":attribute" обязательно для заполнения, когда :values указано.', + 'required_with_all' => 'Поле ":attribute" обязательно для заполнения, когда :values указано.', + 'required_without' => 'Поле ":attribute" обязательно для заполнения, когда :values не указано.', + 'required_without_all' => 'Поле ":attribute" обязательно для заполнения, когда ни одно из :values не указано.', + 'same' => 'Значения полей ":attribute" и ":other" должны совпадать.', + 'size' => [ + 'array' => 'Количество элементов в поле ":attribute" должно быть равным :size.', + 'file' => 'Размер файла в поле ":attribute" должен быть равен :size Килобайт(а).', + 'numeric' => 'Значение поля ":attribute" должно быть равным :size.', + 'string' => 'Количество символов в поле ":attribute" должно быть равным :size.', + ], + 'starts_with' => 'Поле ":attribute" должно начинаться с одного из следующих значений: :values', + 'string' => 'Значение поля ":attribute" должно быть строкой.', + 'timezone' => 'Значение поля ":attribute" должно быть действительным часовым поясом.', + 'unique' => 'Такое значение поля ":attribute" уже существует.', + 'uploaded' => 'Загрузка поля ":attribute" не удалась.', + 'url' => 'Значение поля ":attribute" имеет ошибочный формат URL.', + 'uuid' => 'Значение поля ":attribute" должно быть корректным UUID.', + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + 'cron' => 'Значение поля ":attribute" не корректно.' +]; diff --git a/src/resources/views/components/profile/profile-card.blade.php b/src/resources/views/components/profile/profile-card.blade.php index e69de29..be1e41e 100644 --- a/src/resources/views/components/profile/profile-card.blade.php +++ b/src/resources/views/components/profile/profile-card.blade.php @@ -0,0 +1,212 @@ +@props(['user']) +
+
+
+
+
+ user +
+
+

+ {{ $user->name }} +

+
+

+ {{ $user->role->name }} +

+ {{-- +

+ Arizona, United States. +

--}} +
+
+
+ + +
+
+ + + +
+
+

+ {{ __('Edit') }} +

+

+ {{ __('Update your details to keep your profile up-to-date.') }} +

+
+
+
+
+
+ {{ __('Personal Information') }} +
+ +
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+ + @if(auth()->user()->role->code === 'admin') +
+ + +
+ @endif +
+
+
+
+ + +
+
+
+
+
diff --git a/src/resources/views/components/ui/modal.blade.php b/src/resources/views/components/ui/modal.blade.php new file mode 100644 index 0000000..744e0a0 --- /dev/null +++ b/src/resources/views/components/ui/modal.blade.php @@ -0,0 +1,59 @@ +@props([ + 'isOpen' => false, + 'showCloseButton' => true, +]) + + + + diff --git a/src/resources/views/pages/user/show.blade.php b/src/resources/views/pages/user/show.blade.php index bfc1435..5a4eff0 100644 --- a/src/resources/views/pages/user/show.blade.php +++ b/src/resources/views/pages/user/show.blade.php @@ -1,10 +1,10 @@ @extends ('layouts.app') @section ('content') - +
-

Profile

- +

{{ __('Profile') }}

+
diff --git a/src/routes/web.php b/src/routes/web.php index b04283c..6221578 100644 --- a/src/routes/web.php +++ b/src/routes/web.php @@ -23,6 +23,9 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::get('/{user_uuid}', 'show') ->name('show') ->whereUuid('user_uuid'); + Route::patch('/{user_uuid}/update', 'update') + ->name('update') + ->whereUuid('user_uuid'); }); });