Версия 0.1.0 #1
							
								
								
									
										10
									
								
								app/application/app/Dto/Builder/ProjectFeedback.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/application/app/Dto/Builder/ProjectFeedback.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Dto\Builder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class ProjectFeedback
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								app/application/app/Dto/HttpUserData.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/application/app/Dto/HttpUserData.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Dto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class HttpUserData
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private ?string $clientIp = null,
 | 
				
			||||||
 | 
					        private ?string $userAgent = null,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getClientIp(): ?string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->clientIp;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getUserAgent(): ?string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->userAgent;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Dto\Service\Admin\Project\Feedback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\Builder\ProjectFeedback;
 | 
				
			||||||
 | 
					use App\Dto\Service\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class Index extends Pages
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private ProjectFeedback $projectFeedbackBuilderDto,
 | 
				
			||||||
 | 
					        int $page
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        parent::__construct($page);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getProjectFeedbackBuilderDto(): ProjectFeedback
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->projectFeedbackBuilderDto;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -30,6 +30,14 @@ final class Translations
 | 
				
			|||||||
            'site.Choose language',
 | 
					            'site.Choose language',
 | 
				
			||||||
            'site.Page without translation',
 | 
					            'site.Page without translation',
 | 
				
			||||||
            'site.Project',
 | 
					            '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',
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										36
									
								
								app/application/app/Dto/Service/Site/Feedback/Send.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/application/app/Dto/Service/Site/Feedback/Send.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Dto\Service\Site\Feedback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\HttpUserData;
 | 
				
			||||||
 | 
					use App\Dto\Service\Dto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class Send extends Dto
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private string $message,
 | 
				
			||||||
 | 
					        private HttpUserData $httpUserData,
 | 
				
			||||||
 | 
					        private ?string $name,
 | 
				
			||||||
 | 
					        private ?string $email,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getName(): ?string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->name;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getEmail(): ?string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->email;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getMessage(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->message;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getHttpUserData(): HttpUserData
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->httpUserData;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -11,6 +11,7 @@ enum Permission: string
 | 
				
			|||||||
    case ProjectContent = 'project-content';
 | 
					    case ProjectContent = 'project-content';
 | 
				
			||||||
    case ProjectLink = 'project-link';
 | 
					    case ProjectLink = 'project-link';
 | 
				
			||||||
    case ProjectTranslation = 'project-translation';
 | 
					    case ProjectTranslation = 'project-translation';
 | 
				
			||||||
 | 
					    case ProjectFeedback = 'project-feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getPermissions(): array
 | 
					    public function getPermissions(): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -27,6 +28,9 @@ enum Permission: string
 | 
				
			|||||||
                'view'   => __('permissions.Allowed to watch'),
 | 
					                'view'   => __('permissions.Allowed to watch'),
 | 
				
			||||||
                'update' => __('permissions.Allowed to edit'),
 | 
					                'update' => __('permissions.Allowed to edit'),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
 | 
					            self::ProjectFeedback => [
 | 
				
			||||||
 | 
					                'view'   => __('permissions.Allowed to watch'),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
            default => $this->getBasePermissions()
 | 
					            default => $this->getBasePermissions()
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,8 @@ use App\Models\ProjectLanguage;
 | 
				
			|||||||
enum ProjectSection
 | 
					enum ProjectSection
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    case Home;
 | 
					    case Home;
 | 
				
			||||||
 | 
					    case Feedback;
 | 
				
			||||||
 | 
					    case FeedbackSend;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function url(Project $project, ?ProjectLanguage $language = null): string
 | 
					    public function url(Project $project, ?ProjectLanguage $language = null): string
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -26,6 +28,9 @@ enum ProjectSection
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        $route = match ($this) {
 | 
					        $route = match ($this) {
 | 
				
			||||||
            self::Home => \route($prefixProject . 'home' . $prefixLanguage, $parameters, false),
 | 
					            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;
 | 
					        return $project->http_host . $route;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\Controllers\Admin\Projects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\QuerySettingsDto;
 | 
				
			||||||
 | 
					use App\Http\Controllers\Admin\Controller;
 | 
				
			||||||
 | 
					use App\Http\Requests\Admin\Projects\Feedbacks\IndexRequest;
 | 
				
			||||||
 | 
					use App\Services\Admin\Project\FeedbackService;
 | 
				
			||||||
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class FeedbacksController extends Controller
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private readonly FeedbackService $feedbackService,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function index(IndexRequest $request): View
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $user = $request->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());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\Controllers\Site;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Http\Requests\Site\Feedback\SendRequest;
 | 
				
			||||||
 | 
					use App\Models\ProjectFeedback;
 | 
				
			||||||
 | 
					use App\Services\Site\FeedbackService;
 | 
				
			||||||
 | 
					use Illuminate\Http\RedirectResponse;
 | 
				
			||||||
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class FeedbackController extends Controller
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private readonly FeedbackService $feedbackService,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function index(Request $request): View
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return view('site.feedback.index', [
 | 
				
			||||||
 | 
					            'project'             => $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());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\Requests\Admin\Projects\Feedbacks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Contracts\FormRequestDto;
 | 
				
			||||||
 | 
					use App\Dto\Builder\ProjectFeedback;
 | 
				
			||||||
 | 
					use App\Dto\Service\Admin\Project\Feedback\Index;
 | 
				
			||||||
 | 
					use Illuminate\Foundation\Http\FormRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class IndexRequest extends FormRequest implements FormRequestDto
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the validation rules that apply to the request.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function rules(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'page' => ['nullable', 'numeric', 'min:1']
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getDto(): Index
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return new Index(
 | 
				
			||||||
 | 
					            projectFeedbackBuilderDto: new ProjectFeedback(),
 | 
				
			||||||
 | 
					            page: (int) $this->input('page', 1),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Http\Requests\Site\Feedback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Contracts\FormRequestDto;
 | 
				
			||||||
 | 
					use App\Dto\HttpUserData;
 | 
				
			||||||
 | 
					use App\Dto\Service\Site\Feedback\Send;
 | 
				
			||||||
 | 
					use App\Services\WebsiteTranslations;
 | 
				
			||||||
 | 
					use Illuminate\Foundation\Http\FormRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class SendRequest extends FormRequest implements FormRequestDto
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function attributes(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $attributes = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $websiteTranslations = $this->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),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -57,4 +57,9 @@ final class Project extends Model implements StorageContract
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->hasMany(ProjectLink::class);
 | 
					        return $this->hasMany(ProjectLink::class);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function feedbacks(): HasMany
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->hasMany(ProjectFeedback::class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										52
									
								
								app/application/app/Models/ProjectFeedback.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								app/application/app/Models/ProjectFeedback.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\Scopes\CreatedScope;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Attributes\ScopedBy;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Concerns\HasUuids;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Model;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Relations\BelongsTo;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\SoftDeletes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[ScopedBy([CreatedScope::class])]
 | 
				
			||||||
 | 
					final class ProjectFeedback extends Model
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use HasUuids, HasFactory, SoftDeletes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected $table = 'project_feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The attributes that are mass assignable.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @var array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $fillable = [
 | 
				
			||||||
 | 
					        'name',
 | 
				
			||||||
 | 
					        'email',
 | 
				
			||||||
 | 
					        'message',
 | 
				
			||||||
 | 
					        'ip',
 | 
				
			||||||
 | 
					        'user_agent',
 | 
				
			||||||
 | 
					        'user_id',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the attributes that should be cast.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return array<string, string>
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected function casts(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'name'    => 'encrypted',
 | 
				
			||||||
 | 
					            'email'   => 'encrypted',
 | 
				
			||||||
 | 
					            'message' => 'encrypted',
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function project(): BelongsTo
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->belongsTo(Project::class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								app/application/app/Models/Scopes/CreatedScope.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								app/application/app/Models/Scopes/CreatedScope.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Models\Scopes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Builder;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Model;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Scope;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreatedScope implements Scope
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Apply the scope to a given Eloquent query builder.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function apply(Builder $builder, Model $model): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $builder->orderBy('created_at', 'desc');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								app/application/app/Policies/ProjectFeedbackPolicy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/application/app/Policies/ProjectFeedbackPolicy.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Policies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\ProjectFeedback;
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class ProjectFeedbackPolicy extends Policy
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function viewAny(User $user): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $user->hasPermission('project-feedback.view');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function view(User $user, ProjectFeedback $projectFeedback): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $user->hasPermission('project-feedback.view');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Repositories;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Contracts\Search;
 | 
				
			||||||
 | 
					use App\Dto\Builder\ProjectFeedback as ProjectFeedbackBuilderDto;
 | 
				
			||||||
 | 
					use App\Models\Project;
 | 
				
			||||||
 | 
					use App\Models\ProjectFeedback;
 | 
				
			||||||
 | 
					use App\Services\ProjectFeedback\BuilderCommand;
 | 
				
			||||||
 | 
					use App\Services\Search\CreateSearchInstanceCommand;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class ProjectFeedbackRepository
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private CreateSearchInstanceCommand $createSearchInstanceCommand,
 | 
				
			||||||
 | 
					        private BuilderCommand $builderCommand
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getFeedbacks(ProjectFeedbackBuilderDto $projectFeedbackBuilderDto, array $with = []): Search
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $query = $this->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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Services\Admin\Project;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\Builder\ProjectFeedback as ProjectFeedbackBuilderDto;
 | 
				
			||||||
 | 
					use App\Dto\QuerySettingsDto;
 | 
				
			||||||
 | 
					use App\Models\ProjectFeedback;
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					use App\Repositories\ProjectFeedbackRepository;
 | 
				
			||||||
 | 
					use App\Repositories\ProjectRepository;
 | 
				
			||||||
 | 
					use App\ServiceResults\ServiceResultArray;
 | 
				
			||||||
 | 
					use App\ServiceResults\ServiceResultError;
 | 
				
			||||||
 | 
					use App\Services\Service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class FeedbackService extends Service
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private readonly ProjectFeedbackRepository $projectFeedbackRepository,
 | 
				
			||||||
 | 
					        private readonly ProjectRepository $projectRepository,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function index(ProjectFeedbackBuilderDto $projectFeedbackBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if ($user->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,
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Services\ProjectFeedback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\Builder\ProjectFeedback as ProjectFeedbackBuilderDto;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Builder;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Relations\Relation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class BuilderCommand
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function execute(Relation | Builder $query, ProjectFeedbackBuilderDto $projectFeedbackBuilderDto): Relation | Builder
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $query;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Services\ProjectFeedback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\Project;
 | 
				
			||||||
 | 
					use App\Models\ProjectFeedback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class ProjectFeedbackCommandHandler
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function handleStore(Project $project, array $data): ProjectFeedback
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $feedback = $project->feedbacks()->create($data);
 | 
				
			||||||
 | 
					        return $feedback;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										53
									
								
								app/application/app/Services/Site/FeedbackService.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								app/application/app/Services/Site/FeedbackService.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Services\Site;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\Service\Site\Feedback\Send;
 | 
				
			||||||
 | 
					use App\Models\Project;
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					use App\ServiceResults\ServiceResultError;
 | 
				
			||||||
 | 
					use App\ServiceResults\ServiceResultSuccess;
 | 
				
			||||||
 | 
					use App\Services\ProjectFeedback\ProjectFeedbackCommandHandler;
 | 
				
			||||||
 | 
					use App\Services\Service;
 | 
				
			||||||
 | 
					use App\Services\WebsiteTranslations;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\DB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class FeedbackService extends Service
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private readonly ProjectFeedbackCommandHandler $feedbackCommandHandler,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function send(Send $send, Project $project, WebsiteTranslations $websiteTranslations, ?User $user = null): ServiceResultError | ServiceResultSuccess
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            DB::transaction(function () use ($send, $project, $user) {
 | 
				
			||||||
 | 
					                $data = $this->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, ['<b>', '<strong>', '<p>', '<br>', '<ul>', '<li>', '<ol>', '<table>', '<tr>', '<td>', '<th>', '<i>',]);
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'name'       => $name,
 | 
				
			||||||
 | 
					            'email'      => $send->getEmail(),
 | 
				
			||||||
 | 
					            'message'    => $message,
 | 
				
			||||||
 | 
					            'ip'         => $send->getHttpUserData()->getClientIp(),
 | 
				
			||||||
 | 
					            'user_agent' => $send->getHttpUserData()->getUserAgent(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										26
									
								
								app/application/app/View/Components/Site/Forms/Form.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/application/app/View/Components/Site/Forms/Form.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\View\Components\Site\Forms;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Helpers\Helpers;
 | 
				
			||||||
 | 
					use Illuminate\View\Component;
 | 
				
			||||||
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class Form extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private ?string $requestName = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    abstract protected function getName(): string;
 | 
				
			||||||
 | 
					    abstract public function render(): View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getRequestName(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!is_null($this->requestName)) {
 | 
				
			||||||
 | 
					            return $this->requestName;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->requestName = Helpers::formatAttributeNameToRequestName($this->getName());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->requestName;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								app/application/app/View/Components/Site/Forms/Input.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								app/application/app/View/Components/Site/Forms/Input.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\View\Components\Site\Forms;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Services\WebsiteTranslations;
 | 
				
			||||||
 | 
					use Illuminate\Support\Str;
 | 
				
			||||||
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class Input extends Form
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private readonly WebsiteTranslations $websiteTranslations,
 | 
				
			||||||
 | 
					        private readonly string $title,
 | 
				
			||||||
 | 
					        private readonly string $name,
 | 
				
			||||||
 | 
					        private readonly string $type = 'text',
 | 
				
			||||||
 | 
					        private readonly ?string $value = '',
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getName(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->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(),
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\View\Components\Site\Forms;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Services\WebsiteTranslations;
 | 
				
			||||||
 | 
					use Illuminate\Support\Str;
 | 
				
			||||||
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class TextareaWysiwyg extends Form
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private readonly WebsiteTranslations $websiteTranslations,
 | 
				
			||||||
 | 
					        private readonly string $title,
 | 
				
			||||||
 | 
					        private readonly string $name,
 | 
				
			||||||
 | 
					        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 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(),
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Database\Migrations\Migration;
 | 
				
			||||||
 | 
					use Illuminate\Database\Schema\Blueprint;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return new class extends Migration
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Run the migrations.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function up(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::create('project_feedback', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->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');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -12,4 +12,5 @@ return [
 | 
				
			|||||||
    'Last update' => 'Last update',
 | 
					    'Last update' => 'Last update',
 | 
				
			||||||
    'Links project' => 'Links from the project',
 | 
					    'Links project' => 'Links from the project',
 | 
				
			||||||
    'Translations' => 'Translations',
 | 
					    'Translations' => 'Translations',
 | 
				
			||||||
 | 
					    'Feedback' => 'Feedback',
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,4 +16,5 @@ return [
 | 
				
			|||||||
    'ProjectContent' => 'About the project',
 | 
					    'ProjectContent' => 'About the project',
 | 
				
			||||||
    'ProjectLink'    => 'Links from the project',
 | 
					    'ProjectLink'    => 'Links from the project',
 | 
				
			||||||
    'ProjectTranslation' => 'Translations',
 | 
					    'ProjectTranslation' => 'Translations',
 | 
				
			||||||
 | 
					    'ProjectFeedback'    => 'Feedback',
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,4 +7,13 @@ return [
 | 
				
			|||||||
    'Page without translation' => 'Page without translation',
 | 
					    'Page without translation' => 'Page without translation',
 | 
				
			||||||
    'Powered by service' => 'Powered by the My Projects website engine',
 | 
					    'Powered by service' => 'Powered by the My Projects website engine',
 | 
				
			||||||
    'Choose language' => 'Choose language',
 | 
					    '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',
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,4 +12,5 @@ return [
 | 
				
			|||||||
    'Last update' => 'Последнее обновление',
 | 
					    'Last update' => 'Последнее обновление',
 | 
				
			||||||
    'Links project' => 'Ссылки от проекта',
 | 
					    'Links project' => 'Ссылки от проекта',
 | 
				
			||||||
    'Translations' => 'Переводы',
 | 
					    'Translations' => 'Переводы',
 | 
				
			||||||
 | 
					    'Feedback' => 'Обратная связь',
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,4 +16,5 @@ return [
 | 
				
			|||||||
    'ProjectContent' => 'О проекте',
 | 
					    'ProjectContent' => 'О проекте',
 | 
				
			||||||
    'ProjectLink'    => 'Ссылки от проекта',
 | 
					    'ProjectLink'    => 'Ссылки от проекта',
 | 
				
			||||||
    'ProjectTranslation' => 'Переводы',
 | 
					    'ProjectTranslation' => 'Переводы',
 | 
				
			||||||
 | 
					    'ProjectFeedback'    => 'Обратная связь',
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,4 +7,13 @@ return [
 | 
				
			|||||||
    'Page without translation' => 'Страница без перевода',
 | 
					    'Page without translation' => 'Страница без перевода',
 | 
				
			||||||
    'Powered by service' => 'Работает на движке сайта «Мои проекты»',
 | 
					    'Powered by service' => 'Работает на движке сайта «Мои проекты»',
 | 
				
			||||||
    'Choose language' => 'Выберите язык',
 | 
					    'Choose language' => 'Выберите язык',
 | 
				
			||||||
 | 
					    'Feedback' => 'Обратная связь',
 | 
				
			||||||
 | 
					    'Feedback-send' => 'Отправить отзыв',
 | 
				
			||||||
 | 
					    'required field' => 'обязательное поле',
 | 
				
			||||||
 | 
					    'Message sent successfully' => 'Сообщение успешно отправлено',
 | 
				
			||||||
 | 
					    'attributes' => [
 | 
				
			||||||
 | 
					        'name'    => 'имя',
 | 
				
			||||||
 | 
					        'email'   => 'email',
 | 
				
			||||||
 | 
					        'message' => 'сообщение',
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;1,400;1,700&display=swap');
 | 
				
			||||||
@import "reset";
 | 
					@import "reset";
 | 
				
			||||||
 | 
					@import "forms";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
html,
 | 
					html,
 | 
				
			||||||
body {
 | 
					body {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										86
									
								
								app/application/resources/site/scss/forms.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								app/application/resources/site/scss/forms.scss
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -26,6 +26,22 @@
 | 
				
			|||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
@endcan
 | 
					@endcan
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@can('viewAny', \App\Models\ProjectFeedback::class)
 | 
				
			||||||
 | 
					    <li @class([
 | 
				
			||||||
 | 
					            'nav-item',
 | 
				
			||||||
 | 
					             'active' => request()->route()->named('admin.feedbacks.*'),
 | 
				
			||||||
 | 
					        ])>
 | 
				
			||||||
 | 
					        <a href="{{ route('admin.feedbacks.index') }}" class="nav-link">
 | 
				
			||||||
 | 
					                <span class="sidebar-icon">
 | 
				
			||||||
 | 
					                    <svg class="icon icon-xs me-2" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
 | 
				
			||||||
 | 
					                      <path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"></path>
 | 
				
			||||||
 | 
					                    </svg>
 | 
				
			||||||
 | 
					                </span>
 | 
				
			||||||
 | 
					            <span class="sidebar-text">{{ __('admin-sections.Feedback') }}</span>
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					    </li>
 | 
				
			||||||
 | 
					@endcan
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@can('viewAny', \App\Models\User::class)
 | 
					@can('viewAny', \App\Models\User::class)
 | 
				
			||||||
    <li @class([
 | 
					    <li @class([
 | 
				
			||||||
            'nav-item',
 | 
					            'nav-item',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					@section('meta_title', __('admin-sections.Feedback'))
 | 
				
			||||||
 | 
					@section('h1', __('admin-sections.Feedback'))
 | 
				
			||||||
 | 
					<x-admin.layout>
 | 
				
			||||||
 | 
					    <div class="card border-0 shadow mb-4">
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					            <div class="table-responsive">
 | 
				
			||||||
 | 
					                <table class="table table-centered table-nowrap mb-0 rounded">
 | 
				
			||||||
 | 
					                    <thead class="thead-light">
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                        <th class="border-0">{{ __('admin-sections.Project') }}</th>
 | 
				
			||||||
 | 
					                        <th class="border-0" style="width: 100%;">{{ __('site.attributes.message') }}</th>
 | 
				
			||||||
 | 
					                        <th class="border-0">{{ __('site.attributes.name') }}</th>
 | 
				
			||||||
 | 
					                        <th class="border-0">{{ __('site.attributes.email') }}</th>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                    </thead>
 | 
				
			||||||
 | 
					                    <tbody>
 | 
				
			||||||
 | 
					                    @foreach($feedbacks as $feedback)
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>{{ $feedback->project->name }}</td>
 | 
				
			||||||
 | 
					                            <td>{!! $feedback->message !!}</td>
 | 
				
			||||||
 | 
					                            <td>
 | 
				
			||||||
 | 
					                                <p>{{ $feedback->name }}</p>
 | 
				
			||||||
 | 
					                                <p><strong>IP:</strong> {{ $feedback->ip }}</p>
 | 
				
			||||||
 | 
					                                <p><strong>UserAgent:</strong> {{ $feedback->user_agent }}</p>
 | 
				
			||||||
 | 
					                            </td>
 | 
				
			||||||
 | 
					                            <td>{{ $feedback->email }}</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                    @endforeach
 | 
				
			||||||
 | 
					                    </tbody>
 | 
				
			||||||
 | 
					                </table>
 | 
				
			||||||
 | 
					                <div class="card-footer border-0">
 | 
				
			||||||
 | 
					                    {{ $feedbacks->links() }}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</x-admin.layout>
 | 
				
			||||||
@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					@section('meta_title', __('admin-sections.Project') . ': ' . $project->name . ' - ' . __('admin-sections.Feedback'))
 | 
				
			||||||
 | 
					@section('h1', __('admin-sections.Project') . ': ' . $project->name)
 | 
				
			||||||
 | 
					<x-admin.layout>
 | 
				
			||||||
 | 
					    <div class="card border-0 shadow mb-4">
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					            <h3 id="category" class="mb-4">{{ __('admin-sections.Feedback') }}</h3>
 | 
				
			||||||
 | 
					            <div class="table-responsive">
 | 
				
			||||||
 | 
					                <table class="table table-centered table-nowrap mb-0 rounded">
 | 
				
			||||||
 | 
					                    <thead class="thead-light">
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                        <th class="border-0" style="width: 100%;">{{ __('site.attributes.message') }}</th>
 | 
				
			||||||
 | 
					                        <th class="border-0">{{ __('site.attributes.name') }}</th>
 | 
				
			||||||
 | 
					                        <th class="border-0">{{ __('site.attributes.email') }}</th>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                    </thead>
 | 
				
			||||||
 | 
					                    <tbody>
 | 
				
			||||||
 | 
					                    @foreach($feedbacks as $feedback)
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>{!! $feedback->message !!}</td>
 | 
				
			||||||
 | 
					                            <td>
 | 
				
			||||||
 | 
					                                <p>{{ $feedback->name }}</p>
 | 
				
			||||||
 | 
					                                <p><strong>IP:</strong> {{ $feedback->ip }}</p>
 | 
				
			||||||
 | 
					                                <p><strong>UserAgent:</strong> {{ $feedback->user_agent }}</p>
 | 
				
			||||||
 | 
					                            </td>
 | 
				
			||||||
 | 
					                            <td>{{ $feedback->email }}</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                    @endforeach
 | 
				
			||||||
 | 
					                    </tbody>
 | 
				
			||||||
 | 
					                </table>
 | 
				
			||||||
 | 
					                <div class="card-footer border-0">
 | 
				
			||||||
 | 
					                    {{ $feedbacks->links() }}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</x-admin.layout>
 | 
				
			||||||
@@ -48,6 +48,18 @@
 | 
				
			|||||||
                                </td>
 | 
					                                </td>
 | 
				
			||||||
                            </tr>
 | 
					                            </tr>
 | 
				
			||||||
                        @endcan
 | 
					                        @endcan
 | 
				
			||||||
 | 
					                        @can('viewAny', \App\Models\ProjectFeedback::class)
 | 
				
			||||||
 | 
					                            <tr>
 | 
				
			||||||
 | 
					                                <td>
 | 
				
			||||||
 | 
					                                    <a href="{{ route('admin.projects.feedbacks.index', ['project' => $project->id]) }}" class="fw-bold">
 | 
				
			||||||
 | 
					                                        <svg width="16" height="16" class="align-text-top" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
 | 
				
			||||||
 | 
					                                            <path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"></path>
 | 
				
			||||||
 | 
					                                        </svg>
 | 
				
			||||||
 | 
					                                        {{ __('admin-sections.Feedback') }}
 | 
				
			||||||
 | 
					                                    </a>
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                        @endcan
 | 
				
			||||||
                    </tbody>
 | 
					                    </tbody>
 | 
				
			||||||
                </table>
 | 
					                </table>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					<div class="form-block">
 | 
				
			||||||
 | 
					    <label for="form-input-{{ $requestName }}">
 | 
				
			||||||
 | 
					        {{ $title }}
 | 
				
			||||||
 | 
					        @if($attributes->get('required'))
 | 
				
			||||||
 | 
					            <span>{{ $websiteTranslations->translate('site.required field') }}</span>
 | 
				
			||||||
 | 
					        @endif
 | 
				
			||||||
 | 
					    </label>
 | 
				
			||||||
 | 
					    <input id="form-input-{{ $requestName }}" class="form-control @error($requestName) is-invalid @enderror" name="{{ $name }}" type="{{ $type }}" @if($type !== 'password') value="{{ $value }}" @endif {{ $attributes }}>
 | 
				
			||||||
 | 
					    @error($requestName)
 | 
				
			||||||
 | 
					        <span class="invalid-feedback">{{ $message }}</span>
 | 
				
			||||||
 | 
					    @enderror
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					<div class="form-block">
 | 
				
			||||||
 | 
					    <label for="form-textarea-wysiwyg-{{ $requestName }}">
 | 
				
			||||||
 | 
					        {{ $title }}
 | 
				
			||||||
 | 
					        @if($attributes->get('required'))
 | 
				
			||||||
 | 
					            <span>{{ $websiteTranslations->translate('site.required field') }}</span>
 | 
				
			||||||
 | 
					        @endif
 | 
				
			||||||
 | 
					    </label>
 | 
				
			||||||
 | 
					    <textarea class="form-control textarea-tinymce @error($requestName) is-invalid @enderror" name="{{ $name }}" id="form-textarea-wysiwyg-{{ $requestName }}" rows="3">{{ $value }}</textarea>
 | 
				
			||||||
 | 
					    @error($requestName)
 | 
				
			||||||
 | 
					    <span class="invalid-feedback">{{ $message }}</span>
 | 
				
			||||||
 | 
					    @enderror
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					@pushOnce('scripts')
 | 
				
			||||||
 | 
					    <script src="{{ asset('/build/tinymce/tinymce.min.js') }}" referrerpolicy="origin"></script>
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        document.addEventListener('DOMContentLoaded', () => {
 | 
				
			||||||
 | 
					            tinymce.init({
 | 
				
			||||||
 | 
					                selector: '.textarea-tinymce',
 | 
				
			||||||
 | 
					                @if(in_array(app()->getLocale(), ['ru'], true))
 | 
				
			||||||
 | 
					                    language: '{{ app()->getLocale() }}',
 | 
				
			||||||
 | 
					                @endif
 | 
				
			||||||
 | 
					                license_key: '{{ $tinymceLicenseKey }}',
 | 
				
			||||||
 | 
					                plugins: 'emoticons lists table',
 | 
				
			||||||
 | 
					                toolbar: 'bold italic | bullist numlist table | emoticons',
 | 
				
			||||||
 | 
					                menubar: false,
 | 
				
			||||||
 | 
					                mobile: {
 | 
				
			||||||
 | 
					                    menubar: false,
 | 
				
			||||||
 | 
					                    plugins: 'emoticons lists table',
 | 
				
			||||||
 | 
					                    toolbar: 'bold italic bullist numlist table emoticons',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                referrer_policy: 'origin',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					@endpushonce
 | 
				
			||||||
@@ -37,11 +37,16 @@
 | 
				
			|||||||
                <div class="menu__title">{{ __('site.Menu') }}</div>
 | 
					                <div class="menu__title">{{ __('site.Menu') }}</div>
 | 
				
			||||||
                <ul>
 | 
					                <ul>
 | 
				
			||||||
                    <li><a href="{{ \App\Enums\Site\ProjectSection::Home->url($project, $websiteTranslations->getLanguage()) }}" @class(['active' => request()->route()->named(['home', 'language.home', 'project.home', 'project.language.home'])])>{{ $websiteTranslations->translate('site.About project') }}</a></li>
 | 
					                    <li><a href="{{ \App\Enums\Site\ProjectSection::Home->url($project, $websiteTranslations->getLanguage()) }}" @class(['active' => request()->route()->named(['home', 'language.home', 'project.home', 'project.language.home'])])>{{ $websiteTranslations->translate('site.About project') }}</a></li>
 | 
				
			||||||
 | 
					                    <li><a href="{{ \App\Enums\Site\ProjectSection::Feedback->url($project, $websiteTranslations->getLanguage()) }}" @class(['active' => request()->route()->named(['feedback', 'language.feedback', 'project.feedback', 'project.language.feedback'])])>{{ $websiteTranslations->translate('site.Feedback') }}</a></li>
 | 
				
			||||||
                </ul>
 | 
					                </ul>
 | 
				
			||||||
            </nav>
 | 
					            </nav>
 | 
				
			||||||
            <div class="section-container">
 | 
					            <div class="section-container">
 | 
				
			||||||
                <div class="content">
 | 
					                <div class="content">
 | 
				
			||||||
                    <h1>@yield('h1', '')</h1>
 | 
					                    <h1>@yield('h1', '')</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    @includeWhen($errors->any(), 'layout.site._errors', ['errors' => $errors->all()])
 | 
				
			||||||
 | 
					                    @includeWhen(Session::has('success'), 'layout.site._success', ['success' => Session::get('success')])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    {{ $slot }}
 | 
					                    {{ $slot }}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <footer class="footer">
 | 
					                <footer class="footer">
 | 
				
			||||||
@@ -51,5 +56,6 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    @vite('resources/site/js/app.js')
 | 
					    @vite('resources/site/js/app.js')
 | 
				
			||||||
 | 
					    @stack('scripts')
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					<div class="alert alert-danger">
 | 
				
			||||||
 | 
					    <ul>
 | 
				
			||||||
 | 
					        @foreach($errors as $error)
 | 
				
			||||||
 | 
					            <li>{{ $error }}</li>
 | 
				
			||||||
 | 
					        @endforeach
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					<div class="alert alert-success">
 | 
				
			||||||
 | 
					    {{ $success }}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					@section('meta_title', $websiteTranslations->translate('site.Feedback'))
 | 
				
			||||||
 | 
					@section('h1', $websiteTranslations->translate('site.Feedback'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<x-site.layout :project="$project" :websiteTranslations="$websiteTranslations">
 | 
				
			||||||
 | 
					    @if(Session::has('success') !== true)
 | 
				
			||||||
 | 
					        <form method="post" action="{{ \App\Enums\Site\ProjectSection::FeedbackSend->url($project, $websiteTranslations->getLanguage()) }}">
 | 
				
			||||||
 | 
					            @csrf
 | 
				
			||||||
 | 
					            <x-site.forms.input :title="$websiteTranslations->translate('site.attributes.name')" :websiteTranslations="$websiteTranslations" name="name" type="text" value="" autofocus />
 | 
				
			||||||
 | 
					            <x-site.forms.input :title="$websiteTranslations->translate('site.attributes.email')" :websiteTranslations="$websiteTranslations" name="email" type="text" value="" />
 | 
				
			||||||
 | 
					            <x-site.forms.textarea-wysiwyg :title="$websiteTranslations->translate('site.attributes.message')" :websiteTranslations="$websiteTranslations" required name="message" value="" />
 | 
				
			||||||
 | 
					            <div class="form-block">
 | 
				
			||||||
 | 
					                @captcha
 | 
				
			||||||
 | 
					                @error('captcha-verified')
 | 
				
			||||||
 | 
					                    <span class="invalid-feedback">{{ $message }}</span>
 | 
				
			||||||
 | 
					                @enderror
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="form-block">
 | 
				
			||||||
 | 
					                <button class="button" type="submit">{{ $websiteTranslations->translate('site.Feedback-send') }}</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					    @endif
 | 
				
			||||||
 | 
					</x-site.layout>
 | 
				
			||||||
@@ -4,3 +4,10 @@ use Illuminate\Support\Facades\Route;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Route::get('/', [\App\Http\Controllers\Site\ProjectsController::class, 'index'])->name('home');
 | 
					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('/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');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,8 +31,12 @@ Route::middleware(['auth', 'verified', \App\Http\Middleware\UserLocale::class])-
 | 
				
			|||||||
            Route::get('translations/{language}', [\App\Http\Controllers\Admin\Projects\TranslationsController::class, 'edit'])->name('translations.edit')->where(['language' => '[0-9]+']);
 | 
					            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::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]+']);
 | 
					        })->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');
 | 
					        Route::post('languages/new-language', [\App\Http\Controllers\Admin\LanguagesController::class, 'newLanguage'])->name('new-language');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user