From 8c353a49b7f5e601ed0ee9273a0585e85a5c8282 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Tue, 23 Apr 2024 19:30:56 +0500 Subject: [PATCH] Added Feedback section. --- .../app/Dto/Builder/ProjectFeedback.php | 10 +++ app/application/app/Dto/HttpUserData.php | 21 +++++ .../Service/Admin/Project/Feedback/Index.php | 21 +++++ .../Project/Translation/Translations.php | 8 ++ .../app/Dto/Service/Site/Feedback/Send.php | 36 ++++++++ app/application/app/Enums/Permission.php | 4 + .../app/Enums/Site/ProjectSection.php | 5 ++ .../Admin/Projects/FeedbacksController.php | 52 +++++++++++ .../Controllers/Site/FeedbackController.php | 41 +++++++++ .../Admin/Projects/Feedbacks/IndexRequest.php | 29 +++++++ .../Requests/Site/Feedback/SendRequest.php | 57 ++++++++++++ app/application/app/Models/Project.php | 5 ++ .../app/Models/ProjectFeedback.php | 52 +++++++++++ .../app/Models/Scopes/CreatedScope.php | 18 ++++ .../app/Policies/ProjectFeedbackPolicy.php | 19 ++++ .../ProjectFeedbackRepository.php | 38 ++++++++ .../Admin/Project/FeedbackService.php | 66 ++++++++++++++ .../ProjectFeedback/BuilderCommand.php | 15 ++++ .../ProjectFeedbackCommandHandler.php | 15 ++++ .../app/Services/Site/FeedbackService.php | 53 ++++++++++++ .../app/View/Components/Site/Forms/Form.php | 26 ++++++ .../app/View/Components/Site/Forms/Input.php | 58 +++++++++++++ .../Components/Site/Forms/TextareaWysiwyg.php | 54 ++++++++++++ ...4_04_22_091519_create_project_feedback.php | 40 +++++++++ app/application/lang/en/admin-sections.php | 1 + app/application/lang/en/permissions.php | 1 + app/application/lang/en/site.php | 9 ++ app/application/lang/ru/admin-sections.php | 1 + app/application/lang/ru/permissions.php | 1 + app/application/lang/ru/site.php | 9 ++ app/application/resources/site/scss/app.scss | 1 + .../resources/site/scss/forms.scss | 86 +++++++++++++++++++ .../views/admin/layout/_navigation.blade.php | 16 ++++ .../admin/projects/feedbacks/index.blade.php | 37 ++++++++ .../projects/feedbacks/project.blade.php | 36 ++++++++ .../views/admin/projects/show.blade.php | 12 +++ .../components/site/forms/input.blade.php | 12 +++ .../site/forms/textarea-wysiwyg.blade.php | 36 ++++++++ .../resources/views/layout/site.blade.php | 6 ++ .../views/layout/site/_errors.blade.php | 7 ++ .../views/layout/site/_success.blade.php | 3 + .../views/site/feedback/index.blade.php | 22 +++++ app/application/routes/web-project.php | 7 ++ app/application/routes/web.php | 4 + 44 files changed, 1050 insertions(+) create mode 100644 app/application/app/Dto/Builder/ProjectFeedback.php create mode 100644 app/application/app/Dto/HttpUserData.php create mode 100644 app/application/app/Dto/Service/Admin/Project/Feedback/Index.php create mode 100644 app/application/app/Dto/Service/Site/Feedback/Send.php create mode 100644 app/application/app/Http/Controllers/Admin/Projects/FeedbacksController.php create mode 100644 app/application/app/Http/Controllers/Site/FeedbackController.php create mode 100644 app/application/app/Http/Requests/Admin/Projects/Feedbacks/IndexRequest.php create mode 100644 app/application/app/Http/Requests/Site/Feedback/SendRequest.php create mode 100644 app/application/app/Models/ProjectFeedback.php create mode 100644 app/application/app/Models/Scopes/CreatedScope.php create mode 100644 app/application/app/Policies/ProjectFeedbackPolicy.php create mode 100644 app/application/app/Repositories/ProjectFeedbackRepository.php create mode 100644 app/application/app/Services/Admin/Project/FeedbackService.php create mode 100644 app/application/app/Services/ProjectFeedback/BuilderCommand.php create mode 100644 app/application/app/Services/ProjectFeedback/ProjectFeedbackCommandHandler.php create mode 100644 app/application/app/Services/Site/FeedbackService.php create mode 100644 app/application/app/View/Components/Site/Forms/Form.php create mode 100644 app/application/app/View/Components/Site/Forms/Input.php create mode 100644 app/application/app/View/Components/Site/Forms/TextareaWysiwyg.php create mode 100644 app/application/database/migrations/2024_04_22_091519_create_project_feedback.php create mode 100644 app/application/resources/site/scss/forms.scss create mode 100644 app/application/resources/views/admin/projects/feedbacks/index.blade.php create mode 100644 app/application/resources/views/admin/projects/feedbacks/project.blade.php create mode 100644 app/application/resources/views/components/site/forms/input.blade.php create mode 100644 app/application/resources/views/components/site/forms/textarea-wysiwyg.blade.php create mode 100644 app/application/resources/views/layout/site/_errors.blade.php create mode 100644 app/application/resources/views/layout/site/_success.blade.php create mode 100644 app/application/resources/views/site/feedback/index.blade.php diff --git a/app/application/app/Dto/Builder/ProjectFeedback.php b/app/application/app/Dto/Builder/ProjectFeedback.php new file mode 100644 index 0000000..0186427 --- /dev/null +++ b/app/application/app/Dto/Builder/ProjectFeedback.php @@ -0,0 +1,10 @@ +clientIp; + } + + public function getUserAgent(): ?string + { + return $this->userAgent; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/Feedback/Index.php b/app/application/app/Dto/Service/Admin/Project/Feedback/Index.php new file mode 100644 index 0000000..1352071 --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/Feedback/Index.php @@ -0,0 +1,21 @@ +projectFeedbackBuilderDto; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php b/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php index a0ac031..ed702cb 100644 --- a/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php +++ b/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php @@ -30,6 +30,14 @@ public static function getTranslationCodes(): array 'site.Choose language', 'site.Page without translation', 'site.Project', + 'site.Feedback', + 'site.Feedback-send', + 'site.required field', + 'site.attributes.name', + 'site.attributes.email', + 'site.attributes.message', + 'site.Message sent successfully', + 'Server Error', ]; } } diff --git a/app/application/app/Dto/Service/Site/Feedback/Send.php b/app/application/app/Dto/Service/Site/Feedback/Send.php new file mode 100644 index 0000000..749b8f7 --- /dev/null +++ b/app/application/app/Dto/Service/Site/Feedback/Send.php @@ -0,0 +1,36 @@ +name; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function getMessage(): string + { + return $this->message; + } + + public function getHttpUserData(): HttpUserData + { + return $this->httpUserData; + } +} diff --git a/app/application/app/Enums/Permission.php b/app/application/app/Enums/Permission.php index 652a7d5..f939554 100644 --- a/app/application/app/Enums/Permission.php +++ b/app/application/app/Enums/Permission.php @@ -11,6 +11,7 @@ enum Permission: string case ProjectContent = 'project-content'; case ProjectLink = 'project-link'; case ProjectTranslation = 'project-translation'; + case ProjectFeedback = 'project-feedback'; public function getPermissions(): array { @@ -27,6 +28,9 @@ public function getPermissions(): array 'view' => __('permissions.Allowed to watch'), 'update' => __('permissions.Allowed to edit'), ], + self::ProjectFeedback => [ + 'view' => __('permissions.Allowed to watch'), + ], default => $this->getBasePermissions() }; diff --git a/app/application/app/Enums/Site/ProjectSection.php b/app/application/app/Enums/Site/ProjectSection.php index 5ab5228..740c7d8 100644 --- a/app/application/app/Enums/Site/ProjectSection.php +++ b/app/application/app/Enums/Site/ProjectSection.php @@ -8,6 +8,8 @@ enum ProjectSection { case Home; + case Feedback; + case FeedbackSend; public function url(Project $project, ?ProjectLanguage $language = null): string { @@ -26,6 +28,9 @@ public function url(Project $project, ?ProjectLanguage $language = null): string $route = match ($this) { self::Home => \route($prefixProject . 'home' . $prefixLanguage, $parameters, false), + + self::Feedback => \route($prefixProject . 'feedback' . $prefixLanguage, $parameters, false), + self::FeedbackSend => \route($prefixProject . 'feedback.send' . $prefixLanguage, $parameters, false), }; return $project->http_host . $route; diff --git a/app/application/app/Http/Controllers/Admin/Projects/FeedbacksController.php b/app/application/app/Http/Controllers/Admin/Projects/FeedbacksController.php new file mode 100644 index 0000000..370c3ac --- /dev/null +++ b/app/application/app/Http/Controllers/Admin/Projects/FeedbacksController.php @@ -0,0 +1,52 @@ +user(); + $data = $request->getDto(); + $querySettingsDto = new QuerySettingsDto( + limit: 20, + page: $data->getPage(), + queryWith: ['project'] + ); + + $result = $this->feedbackService->index($data->getProjectFeedbackBuilderDto(), $querySettingsDto, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin.projects.feedbacks.index', $result->getData()); + } + + public function project(int $projectId, IndexRequest $request): View + { + $user = $request->user(); + $data = $request->getDto(); + $querySettingsDto = new QuerySettingsDto( + limit: 20, + page: $data->getPage(), + queryWith: [] + ); + + $result = $this->feedbackService->project($projectId, $data->getProjectFeedbackBuilderDto(), $querySettingsDto, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin.projects.feedbacks.project', $result->getData()); + } +} diff --git a/app/application/app/Http/Controllers/Site/FeedbackController.php b/app/application/app/Http/Controllers/Site/FeedbackController.php new file mode 100644 index 0000000..5448daa --- /dev/null +++ b/app/application/app/Http/Controllers/Site/FeedbackController.php @@ -0,0 +1,41 @@ + $request->get('project'), + 'websiteTranslations' => $request->get('websiteTranslations'), + ]); + } + + public function send(SendRequest $request): RedirectResponse + { + $project = $request->get('project'); + $websiteTranslations = $request->get('websiteTranslations'); + $user = $request->user(); + $data = $request->getDto(); + + $result = $this->feedbackService->send($data, $project, $websiteTranslations, $user); + if ($result->isError()) { + return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage()); + } + + $url = \App\Enums\Site\ProjectSection::Feedback->url($project, $websiteTranslations->getLanguage()); + return redirect($url)->withSuccess($result->getMessage()); + } +} diff --git a/app/application/app/Http/Requests/Admin/Projects/Feedbacks/IndexRequest.php b/app/application/app/Http/Requests/Admin/Projects/Feedbacks/IndexRequest.php new file mode 100644 index 0000000..0ae2b2d --- /dev/null +++ b/app/application/app/Http/Requests/Admin/Projects/Feedbacks/IndexRequest.php @@ -0,0 +1,29 @@ + ['nullable', 'numeric', 'min:1'] + ]; + } + + public function getDto(): Index + { + return new Index( + projectFeedbackBuilderDto: new ProjectFeedback(), + page: (int) $this->input('page', 1), + ); + } +} diff --git a/app/application/app/Http/Requests/Site/Feedback/SendRequest.php b/app/application/app/Http/Requests/Site/Feedback/SendRequest.php new file mode 100644 index 0000000..befefb6 --- /dev/null +++ b/app/application/app/Http/Requests/Site/Feedback/SendRequest.php @@ -0,0 +1,57 @@ +request->get('websiteTranslations', null); + if ($websiteTranslations) { + /** @var WebsiteTranslations $websiteTranslations */ + $attributes = [ + 'name' => $websiteTranslations->translate('site.attributes.name'), + 'email' => $websiteTranslations->translate('site.attributes.email'), + 'message' => $websiteTranslations->translate('site.attributes.message'), + ]; + } + + return $attributes; + } + + /** + * Get the validation rules that apply to the request. + */ + public function rules(): array + { + return [ + 'name' => ['nullable', 'string', 'max:255'], + 'email' => ['nullable', 'string', 'max:255', 'email'], + 'message' => ['required', 'string', 'max:5000'], + 'captcha-verified' => ['captcha'], + ]; + } + + public function getDto(): Send + { + $httpUserData = new HttpUserData( + clientIp: $this->getClientIp(), + userAgent: $this->userAgent(), + ); + + return new Send( + message: $this->input('message'), + httpUserData: $httpUserData, + name: $this->input('name', null), + email: $this->input('email', null), + ); + } +} diff --git a/app/application/app/Models/Project.php b/app/application/app/Models/Project.php index 5e2862b..fc2e671 100644 --- a/app/application/app/Models/Project.php +++ b/app/application/app/Models/Project.php @@ -57,4 +57,9 @@ public function links(): HasMany { return $this->hasMany(ProjectLink::class); } + + public function feedbacks(): HasMany + { + return $this->hasMany(ProjectFeedback::class); + } } diff --git a/app/application/app/Models/ProjectFeedback.php b/app/application/app/Models/ProjectFeedback.php new file mode 100644 index 0000000..04e7e3c --- /dev/null +++ b/app/application/app/Models/ProjectFeedback.php @@ -0,0 +1,52 @@ + + */ + protected function casts(): array + { + return [ + 'name' => 'encrypted', + 'email' => 'encrypted', + 'message' => 'encrypted', + ]; + } + + public function project(): BelongsTo + { + return $this->belongsTo(Project::class); + } +} diff --git a/app/application/app/Models/Scopes/CreatedScope.php b/app/application/app/Models/Scopes/CreatedScope.php new file mode 100644 index 0000000..6206e89 --- /dev/null +++ b/app/application/app/Models/Scopes/CreatedScope.php @@ -0,0 +1,18 @@ +orderBy('created_at', 'desc'); + } +} diff --git a/app/application/app/Policies/ProjectFeedbackPolicy.php b/app/application/app/Policies/ProjectFeedbackPolicy.php new file mode 100644 index 0000000..45f7ffb --- /dev/null +++ b/app/application/app/Policies/ProjectFeedbackPolicy.php @@ -0,0 +1,19 @@ +hasPermission('project-feedback.view'); + } + + public function view(User $user, ProjectFeedback $projectFeedback): bool + { + return $user->hasPermission('project-feedback.view'); + } +} diff --git a/app/application/app/Repositories/ProjectFeedbackRepository.php b/app/application/app/Repositories/ProjectFeedbackRepository.php new file mode 100644 index 0000000..6bb730b --- /dev/null +++ b/app/application/app/Repositories/ProjectFeedbackRepository.php @@ -0,0 +1,38 @@ +builderCommand->execute( + query: ProjectFeedback::query()->with($with), + projectFeedbackBuilderDto: $projectFeedbackBuilderDto + ); + + return $this->createSearchInstanceCommand->execute($query); + } + + public function getFeedbacksByProject(Project $project, ProjectFeedbackBuilderDto $projectFeedbackBuilderDto, array $with = []): Search + { + $query = $this->builderCommand->execute( + query: $project->feedbacks()->with($with), + projectFeedbackBuilderDto: $projectFeedbackBuilderDto + ); + + return $this->createSearchInstanceCommand->execute($query); + } +} diff --git a/app/application/app/Services/Admin/Project/FeedbackService.php b/app/application/app/Services/Admin/Project/FeedbackService.php new file mode 100644 index 0000000..a374b7d --- /dev/null +++ b/app/application/app/Services/Admin/Project/FeedbackService.php @@ -0,0 +1,66 @@ +cannot('viewAny', ProjectFeedback::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $feedbacks = $this->projectFeedbackRepository->getFeedbacks( + $projectFeedbackBuilderDto, + $querySettingsDto->getQueryWith() + )->pagination( + $querySettingsDto->getLimit(), + $querySettingsDto->getPage() + ); + + return $this->result([ + 'feedbacks' => $feedbacks, + ]); + } + + public function project(int $projectId, ProjectFeedbackBuilderDto $projectFeedbackBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray + { + $project = $this->projectRepository->getProjectById($projectId); + if (\is_null($project)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('viewAny', ProjectFeedback::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $feedbacks = $this->projectFeedbackRepository->getFeedbacksByProject( + $project, + $projectFeedbackBuilderDto, + $querySettingsDto->getQueryWith() + )->pagination( + $querySettingsDto->getLimit(), + $querySettingsDto->getPage() + ); + + return $this->result([ + 'project' => $project, + 'feedbacks' => $feedbacks, + ]); + } +} diff --git a/app/application/app/Services/ProjectFeedback/BuilderCommand.php b/app/application/app/Services/ProjectFeedback/BuilderCommand.php new file mode 100644 index 0000000..b4ed779 --- /dev/null +++ b/app/application/app/Services/ProjectFeedback/BuilderCommand.php @@ -0,0 +1,15 @@ +feedbacks()->create($data); + return $feedback; + } +} diff --git a/app/application/app/Services/Site/FeedbackService.php b/app/application/app/Services/Site/FeedbackService.php new file mode 100644 index 0000000..7bbe72f --- /dev/null +++ b/app/application/app/Services/Site/FeedbackService.php @@ -0,0 +1,53 @@ +getDataFeedback($send); + $data['user_id'] = $user?->id; + $this->feedbackCommandHandler->handleStore($project, $data); + }); + } catch (\Throwable $e) { + report($e); + return $this->errService($websiteTranslations->translate('Server Error')); + } + + return $this->ok($websiteTranslations->translate('site.Message sent successfully')); + } + + private function getDataFeedback(Send $send): array + { + $name = $send->getName(); + if ($name !== null) { + $name = strip_tags($name); + } + $message = $send->getMessage(); + $message = strip_tags($message, ['', '', '

', '
', '

    ', '
  • ', '
      ', '', '', ' @endcan + @can('viewAny', \App\Models\ProjectFeedback::class) + + + + @endcan
      ', '', '',]); + return [ + 'name' => $name, + 'email' => $send->getEmail(), + 'message' => $message, + 'ip' => $send->getHttpUserData()->getClientIp(), + 'user_agent' => $send->getHttpUserData()->getUserAgent(), + ]; + } +} diff --git a/app/application/app/View/Components/Site/Forms/Form.php b/app/application/app/View/Components/Site/Forms/Form.php new file mode 100644 index 0000000..956f702 --- /dev/null +++ b/app/application/app/View/Components/Site/Forms/Form.php @@ -0,0 +1,26 @@ +requestName)) { + return $this->requestName; + } + + $this->requestName = Helpers::formatAttributeNameToRequestName($this->getName()); + + return $this->requestName; + } +} diff --git a/app/application/app/View/Components/Site/Forms/Input.php b/app/application/app/View/Components/Site/Forms/Input.php new file mode 100644 index 0000000..b3a7383 --- /dev/null +++ b/app/application/app/View/Components/Site/Forms/Input.php @@ -0,0 +1,58 @@ +name; + } + + private function getTitle(): string + { + return Str::ucfirst($this->title); + } + + private function getType(): string + { + return $this->type; + } + + private function getValue(): string + { + return (string) old($this->getRequestName(), $this->value); + } + + private function getWebsiteTranslations(): WebsiteTranslations + { + return $this->websiteTranslations; + } + + /** + * @inheritDoc + */ + public function render(): View + { + return view('components.site.forms.input', [ + 'websiteTranslations' => $this->getWebsiteTranslations(), + 'title' => $this->getTitle(), + 'name' => $this->getName(), + 'requestName' => $this->getRequestName(), + 'type' => $this->getType(), + 'value' => $this->getValue(), + ]); + } +} diff --git a/app/application/app/View/Components/Site/Forms/TextareaWysiwyg.php b/app/application/app/View/Components/Site/Forms/TextareaWysiwyg.php new file mode 100644 index 0000000..3e0922b --- /dev/null +++ b/app/application/app/View/Components/Site/Forms/TextareaWysiwyg.php @@ -0,0 +1,54 @@ +name; + } + + private function getTitle(): string + { + return Str::ucfirst($this->title); + } + + private function getValue(): string + { + return (string) old($this->getRequestName(), $this->value); + } + + private function getWebsiteTranslations(): WebsiteTranslations + { + return $this->websiteTranslations; + } + + /** + * @inheritDoc + */ + public function render(): View + { + $tinymceLicenseKey = config('tinymce.license_key'); + + return view('components.site.forms.textarea-wysiwyg', [ + 'websiteTranslations' => $this->getWebsiteTranslations(), + 'tinymceLicenseKey' => $tinymceLicenseKey, + 'title' => $this->getTitle(), + 'name' => $this->getName(), + 'requestName' => $this->getRequestName(), + 'value' => $this->getValue(), + ]); + } +} diff --git a/app/application/database/migrations/2024_04_22_091519_create_project_feedback.php b/app/application/database/migrations/2024_04_22_091519_create_project_feedback.php new file mode 100644 index 0000000..08e278d --- /dev/null +++ b/app/application/database/migrations/2024_04_22_091519_create_project_feedback.php @@ -0,0 +1,40 @@ +uuid('id')->primary(); + $table->unsignedBigInteger('project_id')->index(); + $table->foreign('project_id')->references('id')->on('projects'); + $table->unsignedBigInteger('user_id')->nullable()->index(); + $table->foreign('user_id')->references('id')->on('users'); + $table->text('name')->nullable(); + $table->text('email')->nullable(); + $table->text('message'); + $table->ipAddress('ip')->nullable(); + $table->string('user_agent')->nullable(); + $table->timestamps(); + $table->softDeletes(); + $table->index(['created_at']); + $table->index(['project_id', 'deleted_at']); + $table->index(['user_id', 'deleted_at']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('project_feedback'); + } +}; diff --git a/app/application/lang/en/admin-sections.php b/app/application/lang/en/admin-sections.php index a4283b1..1fd967f 100644 --- a/app/application/lang/en/admin-sections.php +++ b/app/application/lang/en/admin-sections.php @@ -12,4 +12,5 @@ 'Last update' => 'Last update', 'Links project' => 'Links from the project', 'Translations' => 'Translations', + 'Feedback' => 'Feedback', ]; diff --git a/app/application/lang/en/permissions.php b/app/application/lang/en/permissions.php index 8800e50..31c6a21 100644 --- a/app/application/lang/en/permissions.php +++ b/app/application/lang/en/permissions.php @@ -16,4 +16,5 @@ 'ProjectContent' => 'About the project', 'ProjectLink' => 'Links from the project', 'ProjectTranslation' => 'Translations', + 'ProjectFeedback' => 'Feedback', ]; diff --git a/app/application/lang/en/site.php b/app/application/lang/en/site.php index 7dded98..edb4844 100644 --- a/app/application/lang/en/site.php +++ b/app/application/lang/en/site.php @@ -7,4 +7,13 @@ 'Page without translation' => 'Page without translation', 'Powered by service' => 'Powered by the My Projects website engine', 'Choose language' => 'Choose language', + 'Feedback' => 'Feedback', + 'Feedback-send' => 'Send review', + 'required field' => 'required field', + 'Message sent successfully' => 'Message sent successfully', + 'attributes' => [ + 'name' => 'name', + 'email' => 'email', + 'message' => 'message', + ], ]; diff --git a/app/application/lang/ru/admin-sections.php b/app/application/lang/ru/admin-sections.php index d8751b7..c900541 100644 --- a/app/application/lang/ru/admin-sections.php +++ b/app/application/lang/ru/admin-sections.php @@ -12,4 +12,5 @@ 'Last update' => 'Последнее обновление', 'Links project' => 'Ссылки от проекта', 'Translations' => 'Переводы', + 'Feedback' => 'Обратная связь', ]; diff --git a/app/application/lang/ru/permissions.php b/app/application/lang/ru/permissions.php index 03d89bf..cd7954a 100644 --- a/app/application/lang/ru/permissions.php +++ b/app/application/lang/ru/permissions.php @@ -16,4 +16,5 @@ 'ProjectContent' => 'О проекте', 'ProjectLink' => 'Ссылки от проекта', 'ProjectTranslation' => 'Переводы', + 'ProjectFeedback' => 'Обратная связь', ]; diff --git a/app/application/lang/ru/site.php b/app/application/lang/ru/site.php index 380df94..c7fa6e4 100644 --- a/app/application/lang/ru/site.php +++ b/app/application/lang/ru/site.php @@ -7,4 +7,13 @@ 'Page without translation' => 'Страница без перевода', 'Powered by service' => 'Работает на движке сайта «Мои проекты»', 'Choose language' => 'Выберите язык', + 'Feedback' => 'Обратная связь', + 'Feedback-send' => 'Отправить отзыв', + 'required field' => 'обязательное поле', + 'Message sent successfully' => 'Сообщение успешно отправлено', + 'attributes' => [ + 'name' => 'имя', + 'email' => 'email', + 'message' => 'сообщение', + ], ]; diff --git a/app/application/resources/site/scss/app.scss b/app/application/resources/site/scss/app.scss index 0b3f177..8842c1b 100644 --- a/app/application/resources/site/scss/app.scss +++ b/app/application/resources/site/scss/app.scss @@ -1,5 +1,6 @@ @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;1,400;1,700&display=swap'); @import "reset"; +@import "forms"; html, body { diff --git a/app/application/resources/site/scss/forms.scss b/app/application/resources/site/scss/forms.scss new file mode 100644 index 0000000..4b76b5d --- /dev/null +++ b/app/application/resources/site/scss/forms.scss @@ -0,0 +1,86 @@ +.form-block { + padding: 10px 0; + + label { + display: block; + font-size: 16px; + font-weight: 600; + padding-bottom: 7px; + color: #000; + + span { + font-size: 14px; + font-weight: 400; + color: #555; + } + } + + .form-control { + display: block; + width: 100%; + padding: 6px 12px; + border-radius: 6px; + border: 1px solid #ddd; + } + + .form-control.is-invalid { + border-color: #dc3545; + } + + .form-control:focus, + .form-control:focus-visible { + outline: 2px solid #006ce7; + } + + .invalid-feedback { + display: block; + padding: 7px 10px; + color: #dc3545; + font-size: 14px; + border: 1px solid #dc3545; + border-radius: 6px; + margin-top: 10px; + margin-bottom: 10px; + } +} + +.button { + background: #006ce7; + border: 1px solid #002452; + border-radius: 6px; + cursor: pointer; + font-weight: 600; + font-size: 16px; + padding: 14px 24px; + color: #fff; +} +.button:hover { + opacity: 0.8; + border-color: #006ce7; + color: #ccc; +} + +.captcha-service-kor-elf { + position: relative; + z-index: 10; +} + +.alert { + border-radius: 6px; + font-size: 16px; + padding: 14px 24px; + + ul { + padding: 0; + margin: 0; + list-style: none; + } +} +.alert.alert-success { + border: 1px solid #0a6f4d; + color: #0a6f4d; +} +.alert.alert-danger { + border: 1px solid #dc3545; + color: #dc3545; +} diff --git a/app/application/resources/views/admin/layout/_navigation.blade.php b/app/application/resources/views/admin/layout/_navigation.blade.php index 146d8a4..2312c1b 100644 --- a/app/application/resources/views/admin/layout/_navigation.blade.php +++ b/app/application/resources/views/admin/layout/_navigation.blade.php @@ -26,6 +26,22 @@ @endcan +@can('viewAny', \App\Models\ProjectFeedback::class) +
    1. request()->route()->named('admin.feedbacks.*'), + ])> + + + + + {{ __('admin-sections.Feedback') }} + +
    2. +@endcan + @can('viewAny', \App\Models\User::class)
    3. +
      +
      +
      + + + + + + + + + + + @foreach($feedbacks as $feedback) + + + + + + + @endforeach + +
      {{ __('admin-sections.Project') }}{{ __('site.attributes.message') }}{{ __('site.attributes.name') }}{{ __('site.attributes.email') }}
      {{ $feedback->project->name }}{!! $feedback->message !!} +

      {{ $feedback->name }}

      +

      IP: {{ $feedback->ip }}

      +

      UserAgent: {{ $feedback->user_agent }}

      +
      {{ $feedback->email }}
      + +
      +
      +
      + diff --git a/app/application/resources/views/admin/projects/feedbacks/project.blade.php b/app/application/resources/views/admin/projects/feedbacks/project.blade.php new file mode 100644 index 0000000..9b7a4b5 --- /dev/null +++ b/app/application/resources/views/admin/projects/feedbacks/project.blade.php @@ -0,0 +1,36 @@ +@section('meta_title', __('admin-sections.Project') . ': ' . $project->name . ' - ' . __('admin-sections.Feedback')) +@section('h1', __('admin-sections.Project') . ': ' . $project->name) + +
      +
      +

      {{ __('admin-sections.Feedback') }}

      +
      + + + + + + + + + + @foreach($feedbacks as $feedback) + + + + + + @endforeach + +
      {{ __('site.attributes.message') }}{{ __('site.attributes.name') }}{{ __('site.attributes.email') }}
      {!! $feedback->message !!} +

      {{ $feedback->name }}

      +

      IP: {{ $feedback->ip }}

      +

      UserAgent: {{ $feedback->user_agent }}

      +
      {{ $feedback->email }}
      + +
      +
      +
      +
      diff --git a/app/application/resources/views/admin/projects/show.blade.php b/app/application/resources/views/admin/projects/show.blade.php index fe9ecbe..1217b98 100644 --- a/app/application/resources/views/admin/projects/show.blade.php +++ b/app/application/resources/views/admin/projects/show.blade.php @@ -48,6 +48,18 @@
    4. + + + {{ __('admin-sections.Feedback') }} + +
      diff --git a/app/application/resources/views/components/site/forms/input.blade.php b/app/application/resources/views/components/site/forms/input.blade.php new file mode 100644 index 0000000..602c7cb --- /dev/null +++ b/app/application/resources/views/components/site/forms/input.blade.php @@ -0,0 +1,12 @@ +
      + + + @error($requestName) + {{ $message }} + @enderror +
      diff --git a/app/application/resources/views/components/site/forms/textarea-wysiwyg.blade.php b/app/application/resources/views/components/site/forms/textarea-wysiwyg.blade.php new file mode 100644 index 0000000..24646e5 --- /dev/null +++ b/app/application/resources/views/components/site/forms/textarea-wysiwyg.blade.php @@ -0,0 +1,36 @@ +
      + + + @error($requestName) + {{ $message }} + @enderror +
      +@pushOnce('scripts') + + +@endpushonce diff --git a/app/application/resources/views/layout/site.blade.php b/app/application/resources/views/layout/site.blade.php index 120e4c6..570c009 100644 --- a/app/application/resources/views/layout/site.blade.php +++ b/app/application/resources/views/layout/site.blade.php @@ -37,11 +37,16 @@

      @yield('h1', '')

      + + @includeWhen($errors->any(), 'layout.site._errors', ['errors' => $errors->all()]) + @includeWhen(Session::has('success'), 'layout.site._success', ['success' => Session::get('success')]) + {{ $slot }}
      @@ -51,5 +56,6 @@
      @vite('resources/site/js/app.js') + @stack('scripts') diff --git a/app/application/resources/views/layout/site/_errors.blade.php b/app/application/resources/views/layout/site/_errors.blade.php new file mode 100644 index 0000000..a7d2f91 --- /dev/null +++ b/app/application/resources/views/layout/site/_errors.blade.php @@ -0,0 +1,7 @@ +
      +
        + @foreach($errors as $error) +
      • {{ $error }}
      • + @endforeach +
      +
      diff --git a/app/application/resources/views/layout/site/_success.blade.php b/app/application/resources/views/layout/site/_success.blade.php new file mode 100644 index 0000000..55f0f4c --- /dev/null +++ b/app/application/resources/views/layout/site/_success.blade.php @@ -0,0 +1,3 @@ +
      + {{ $success }} +
      diff --git a/app/application/resources/views/site/feedback/index.blade.php b/app/application/resources/views/site/feedback/index.blade.php new file mode 100644 index 0000000..fae1980 --- /dev/null +++ b/app/application/resources/views/site/feedback/index.blade.php @@ -0,0 +1,22 @@ +@section('meta_title', $websiteTranslations->translate('site.Feedback')) +@section('h1', $websiteTranslations->translate('site.Feedback')) + + + @if(Session::has('success') !== true) +
      + @csrf + + + +
      + @captcha + @error('captcha-verified') + {{ $message }} + @enderror +
      +
      + +
      + + @endif +
      diff --git a/app/application/routes/web-project.php b/app/application/routes/web-project.php index 5692160..bd15ea9 100644 --- a/app/application/routes/web-project.php +++ b/app/application/routes/web-project.php @@ -4,3 +4,10 @@ Route::get('/', [\App\Http\Controllers\Site\ProjectsController::class, 'index'])->name('home'); Route::get('/language/{language}', [\App\Http\Controllers\Site\ProjectsController::class, 'index'])->name('home-language'); + +Route::get('feedback', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback'); +Route::get('feedback/language/{language}', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback-language'); +Route::get('feedback/success', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback.success'); +Route::get('feedback/success/language/{language}', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback.success-language'); +Route::post('feedback', [\App\Http\Controllers\Site\FeedbackController::class, 'send'])->name('feedback.send'); +Route::post('feedback/language/{language}', [\App\Http\Controllers\Site\FeedbackController::class, 'send'])->name('feedback.send-language'); diff --git a/app/application/routes/web.php b/app/application/routes/web.php index 48d8468..9ad267d 100644 --- a/app/application/routes/web.php +++ b/app/application/routes/web.php @@ -31,8 +31,12 @@ Route::get('translations/{language}', [\App\Http\Controllers\Admin\Projects\TranslationsController::class, 'edit'])->name('translations.edit')->where(['language' => '[0-9]+']); Route::post('translations/{language}', [\App\Http\Controllers\Admin\Projects\TranslationsController::class, 'update'])->name('translations.update')->where(['language' => '[0-9]+']); + Route::get('feedbacks', [\App\Http\Controllers\Admin\Projects\FeedbacksController::class, 'project'])->name('feedbacks.index'); + })->where(['project' => '[0-9]+']); + Route::get('feedbacks', [\App\Http\Controllers\Admin\Projects\FeedbacksController::class, 'index'])->name('feedbacks.index'); + Route::post('languages/new-language', [\App\Http\Controllers\Admin\LanguagesController::class, 'newLanguage'])->name('new-language'); });