Added the ability to add links to the project.

This commit is contained in:
Leonid Nikitin 2024-04-15 22:53:34 +05:00
parent 4583908230
commit 63ea5dac92
Signed by: kor-elf
GPG Key ID: 3C0F720C170F6E1D
29 changed files with 756 additions and 13 deletions

View File

@ -0,0 +1,10 @@
<?php declare(strict_types=1);
namespace App\Dto\Builder;
final readonly class ProjectLink
{
public function __construct(
) { }
}

View File

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
namespace App\Dto\Service\Admin\Project\Link;
use App\Dto\Builder\ProjectLink;
use App\Dto\Service\Pages;
final readonly class Index extends Pages
{
public function __construct(
private ProjectLink $linkBuilderDto,
int $page,
) {
parent::__construct($page);
}
public function getLinkBuilderDto(): ProjectLink
{
return $this->linkBuilderDto;
}
}

View File

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace App\Dto\Service\Admin\Project\Link;
use App\Dto\Service\Dto;
final readonly class StoreUpdate extends Dto
{
public function __construct(
private string $title,
private string $link,
private int $sort,
private ?int $languageId,
) { }
public function getTitle(): string
{
return $this->title;
}
public function getLink(): string
{
return $this->link;
}
public function getSort(): int
{
return $this->sort;
}
public function getLanguageId(): ?int
{
return $this->languageId;
}
}

View File

@ -9,6 +9,7 @@ enum Permission: string
case User = 'user'; case User = 'user';
case Project = 'project'; case Project = 'project';
case ProjectContent = 'project-content'; case ProjectContent = 'project-content';
case ProjectLink = 'project-link';
public function getPermissions(): array public function getPermissions(): array
{ {

View File

@ -0,0 +1,100 @@
<?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\Links\IndexRequest;
use App\Http\Requests\Admin\Projects\Links\StoreUpdateRequest;
use App\Services\Admin\Project\LinkService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
final class LinksController extends Controller
{
public function __construct(
private readonly LinkService $linkService
) { }
public function index(int $projectId, IndexRequest $request): View
{
$user = $request->user();
$data = $request->getDto();
$querySettingsDto = new QuerySettingsDto(
limit: 20,
page: $data->getPage(),
queryWith: ['language']
);
$result = $this->linkService->index($projectId, $data->getLinkBuilderDto(), $querySettingsDto, $user);
if ($result->isError()) {
$this->errors($result);
}
return view('admin/projects/links/index', $result->getData());
}
public function create(int $projectId, Request $request): View
{
$user = $request->user();
$result = $this->linkService->create($projectId, $user);
if ($result->isError()) {
$this->errors($result);
}
return view('admin/projects/links/create', $result->getData());
}
public function edit(int $projectId, int $id, Request $request): View
{
$user = $request->user();
$result = $this->linkService->edit($projectId, $id, $user);
if ($result->isError()) {
$this->errors($result);
}
return view('admin/projects/links/edit', $result->getData());
}
public function store(int $projectId, StoreUpdateRequest $request): RedirectResponse
{
$data = $request->getDto();
$user = $request->user();
$result = $this->linkService->store($projectId, $data, $user);
if ($result->isError()) {
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
}
return redirect()->route('admin.projects.links.edit', [
'project' => $projectId,
'link' => $result->getModel()->id,
])->withSuccess($result->getMessage());
}
public function update(int $projectId, int $id, StoreUpdateRequest $request): RedirectResponse
{
$data = $request->getDto();
$user = $request->user();
$result = $this->linkService->update($projectId, $id, $data, $user);
if ($result->isError()) {
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
}
return redirect()->route('admin.projects.links.edit', [
'project' => $projectId,
'link' => $result->getModel()->id,
])->withSuccess($result->getMessage());
}
public function destroy(int $projectId, int $id, Request $request): RedirectResponse
{
$user = $request->user();
$result = $this->linkService->destroy($projectId, $id, $user);
if ($result->isError()) {
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
}
return redirect()->route('admin.projects.links.index', ['project' => $projectId])->withSuccess($result->getMessage());
}
}

View File

@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace App\Http\Requests\Admin\Projects\Links;
use App\Contracts\FormRequestDto;
use App\Dto\Builder\ProjectLink;
use App\Dto\Service\Admin\Project\Link\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(
linkBuilderDto: new ProjectLink(),
page: (int) $this->input('page', 1),
);
}
}

View File

@ -0,0 +1,38 @@
<?php declare(strict_types=1);
namespace App\Http\Requests\Admin\Projects\Links;
use App\Contracts\FormRequestDto;
use App\Dto\Service\Admin\Project\Link\StoreUpdate;
use Illuminate\Foundation\Http\FormRequest;
final class StoreUpdateRequest extends FormRequest implements FormRequestDto
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'link' => ['required', 'string', 'max:255', 'url:http,https'],
'sort' => ['required', 'integer', 'min:-1000', 'max:1000'],
'language_id' => ['nullable', 'integer', 'min:1'],
];
}
public function getDto(): StoreUpdate
{
$languageId = $this->input('language_id');
if (! \is_null($languageId)) {
$languageId = (int) $languageId;
}
return new StoreUpdate(
title: $this->input('title'),
link: $this->input('link'),
sort: (int) $this->input('sort'),
languageId: $languageId,
);
}
}

View File

@ -52,4 +52,9 @@ public function contents(): HasMany
{ {
return $this->hasMany(ProjectContent::class); return $this->hasMany(ProjectContent::class);
} }
public function links(): HasMany
{
return $this->hasMany(ProjectLink::class);
}
} }

View File

@ -0,0 +1,46 @@
<?php declare(strict_types=1);
namespace App\Models;
use App\Models\Scopes\SortScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
#[ScopedBy([SortScope::class])]
final class ProjectLink extends Model
{
use HasFactory, SoftDeletes;
protected $table = 'project_links';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
/**
* The model's default values for attributes.
*
* @var array
*/
protected $attributes = [
'sort' => 100,
];
protected $fillable = [
'title',
'link',
'sort',
'language_id',
];
public function language(): BelongsTo
{
return $this->belongsTo(ProjectLanguage::class);
}
}

View File

@ -0,0 +1,34 @@
<?php declare(strict_types=1);
namespace App\Policies;
use App\Models\ProjectLink;
use App\Models\User;
final readonly class ProjectLinkPolicy extends Policy
{
public function viewAny(User $user): bool
{
return $user->hasPermission('project-link.view');
}
public function view(User $user, ProjectLink $link): bool
{
return $user->hasPermission('project-link.view');
}
public function create(User $user): bool
{
return $user->hasPermission('project-link.create');
}
public function update(User $user, ProjectLink $link): bool
{
return $user->hasPermission('project-link.update');
}
public function delete(User $user, ProjectLink $link): bool
{
return $user->hasPermission('project-link.delete');
}
}

View File

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
namespace App\Repositories;
use App\Contracts\Search;
use App\Services\ProjectLink\BuilderCommand;
use App\Services\Search\CreateSearchInstanceCommand;
use App\Dto\Builder\ProjectLink as ProjectLinkBuilderDto;
use App\Models\ProjectLink;
final readonly class ProjectLinkRepository
{
public function __construct(
private CreateSearchInstanceCommand $createSearchInstanceCommand,
private BuilderCommand $builderCommand
) { }
public function getLinksFromProject(int $projectId, ProjectLinkBuilderDto $projectLinkBuilderDto, array $with = []): Search
{
$query = $this->builderCommand->execute(
query: ProjectLink::query()->where('project_id', $projectId)->with($with),
projectLinkBuilderDto: $projectLinkBuilderDto,
);
return $this->createSearchInstanceCommand->execute($query);
}
public function getLinkById(int $id): ?ProjectLink
{
return ProjectLink::query()->where('id', $id)->first();
}
}

View File

@ -0,0 +1,203 @@
<?php declare(strict_types=1);
namespace App\Services\Admin\Project;
use App\Dto\Builder\ProjectLink as ProjectLinkBuilderDto;
use App\Dto\QuerySettingsDto;
use App\Dto\Service\Admin\Project\Link\StoreUpdate;
use App\Models\ProjectLink;
use App\Models\User;
use App\Repositories\ProjectLinkRepository;
use App\Repositories\ProjectRepository;
use App\Services\ProjectLink\ProjectLinkCommandHandler;
use App\Services\Service;
use Illuminate\Support\Facades\DB;
use App\ServiceResults\ServiceResultArray;
use App\ServiceResults\ServiceResultError;
use App\ServiceResults\ServiceResultSuccess;
use App\ServiceResults\StoreUpdateResult;
final class LinkService extends Service
{
public function __construct(
private readonly ProjectRepository $projectRepository,
private readonly ProjectLinkRepository $projectLinkRepository,
private readonly ProjectLinkCommandHandler $projectLinkCommandHandler,
) { }
public function index(int $projectId, ProjectLinkBuilderDto $linkBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
{
$project = $this->projectRepository->getProjectById($projectId);
if (\is_null($project)) {
return $this->errNotFound(__('Not Found'));
}
if ($user->cannot('viewAny', ProjectLink::class)) {
return $this->errFobidden(__('Access is denied'));
}
$links = $this->projectLinkRepository->getLinksFromProject(
$project->id,
$linkBuilderDto,
$querySettingsDto->getQueryWith()
)->pagination(
$querySettingsDto->getLimit(),
$querySettingsDto->getPage()
);
return $this->result([
'links' => $links,
'project' => $project,
]);
}
public function create(int $projectId, User $user): ServiceResultError | ServiceResultArray
{
$project = $this->projectRepository->getProjectById($projectId);
if (\is_null($project)) {
return $this->errNotFound(__('Not Found'));
}
if ($user->cannot('create', ProjectLink::class)) {
return $this->errFobidden(__('Access is denied'));
}
return $this->result([
'link' => new ProjectLink(),
'project' => $project,
]);
}
public function edit(int $projectId, int $linkId, User $user): ServiceResultError | ServiceResultArray
{
$project = $this->projectRepository->getProjectById($projectId);
if (\is_null($project)) {
return $this->errNotFound(__('Not Found'));
}
$link = $this->projectLinkRepository->getLinkById($linkId);
if (\is_null($link)) {
return $this->errNotFound(__('Not Found'));
}
if ($user->cannot('view', $link)) {
return $this->errFobidden(__('Access is denied'));
}
return $this->result([
'link' => $link,
'project' => $project,
]);
}
public function store(int $projectId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
{
if ($user->cannot('create', ProjectLink::class)) {
return $this->errFobidden(__('Access is denied'));
}
$project = $this->projectRepository->getProjectById($projectId);
if (\is_null($project)) {
return $this->errNotFound(__('Not Found'));
}
if (
$data->getLanguageId() !== null
&& $project->languages()->where('id', $data->getLanguageId())->exists() === false
) {
return $this->errValidate(
__('validation.exists', ['attribute' => __('validation.attributes.language_id')]),
['language_id' => __('validation.exists', ['attribute' => __('validation.attributes.language_id')])]
);
}
try {
$link = DB::transaction(function () use ($data, $project, $user) {
$dataLink = $this->getDataLink($data);
return $this->projectLinkCommandHandler->handleStore($project, $dataLink);
});
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
}
return $this->resultStoreUpdateModel($link, __('The link was successfully created'));
}
public function update(int $projectId, int $linkId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
{
$project = $this->projectRepository->getProjectById($projectId);
if (\is_null($project)) {
return $this->errNotFound(__('Not Found'));
}
if (
$data->getLanguageId() !== null
&& $project->languages()->where('id', $data->getLanguageId())->exists() === false
) {
return $this->errValidate(
__('validation.exists', ['attribute' => __('validation.attributes.language_id')]),
['language_id' => __('validation.exists', ['attribute' => __('validation.attributes.language_id')])]
);
}
$link = $this->projectLinkRepository->getLinkById($linkId);
if (\is_null($link)) {
return $this->errNotFound(__('Not Found'));
}
if ($user->cannot('update', $link)) {
return $this->errFobidden(__('Access is denied'));
}
try {
$link = DB::transaction(function () use ($data, $link) {
$dataLink = $this->getDataLink($data);
return $this->projectLinkCommandHandler->handleUpdate($link, $dataLink);
});
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
}
return $this->resultStoreUpdateModel($link, __('The link was successfully updated'));
}
public function destroy(int $projectId, int $linkId, User $user): ServiceResultError|ServiceResultSuccess
{
$project = $this->projectRepository->getProjectById($projectId);
if (\is_null($project)) {
return $this->errNotFound(__('Not Found'));
}
$link = $this->projectLinkRepository->getLinkById($linkId);
if (\is_null($link)) {
return $this->errNotFound(__('Not Found'));
}
if ($user->cannot('delete', $link)) {
return $this->errFobidden(__('Access is denied'));
}
try {
DB::transaction(function () use ($link) {
$this->projectLinkCommandHandler->handleDestroy($link);
});
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
}
return $this->ok(__('The link has been deleted'));
}
private function getDataLink(StoreUpdate $data): array
{
return [
'title' => $data->getTitle(),
'link' => $data->getLink(),
'sort' => $data->getSort(),
'language_id' => $data->getLanguageId(),
];
}
}

View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace App\Services\ProjectLink;
use App\Dto\Builder\ProjectLink as ProjectLinkBuilderDto;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
final readonly class BuilderCommand
{
public function execute(Relation | Builder $query, ProjectLinkBuilderDto $projectLinkBuilderDto): Relation | Builder
{
return $query;
}
}

View File

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace App\Services\ProjectLink;
use App\Models\Project;
use App\Models\ProjectLink;
final readonly class ProjectLinkCommandHandler
{
public function handleStore(Project $project, array $data): ProjectLink
{
return $project->links()->create($data);
}
public function handleUpdate(ProjectLink $link, array $data): ProjectLink
{
$link->update($data);
return $link;
}
public function handleDestroy(ProjectLink $link): void
{
$link->delete();
}
}

View File

@ -249,5 +249,9 @@
"There was an error adding a language": "There was an error adding a language", "There was an error adding a language": "There was an error adding a language",
"Select images": "Select images", "Select images": "Select images",
"loading": "loading", "loading": "loading",
"Project information has been successfully updated": "Project information has been successfully updated" "Project information has been successfully updated": "Project information has been successfully updated",
"in all languages": "in all languages",
"The link was successfully created": "The link was successfully created",
"The link was successfully updated": "The link was successfully updated",
"The link has been deleted": "The link has been deleted",
} }

View File

@ -10,4 +10,5 @@
'About project' => 'About the project', 'About project' => 'About the project',
'Languages' => 'Languages', 'Languages' => 'Languages',
'Last update' => 'Last update', 'Last update' => 'Last update',
'Links project' => 'Links from the project',
]; ];

View File

@ -3,12 +3,16 @@
return [ return [
'Role' => 'User group', 'Role' => 'User group',
'User' => 'Users', 'User' => 'Users',
'Allowed to watch' => 'Allowed to watch',
'Allowed to watch' => 'Allowed to watch',
'Allowed to create' => 'Allowed to create', 'Allowed to create' => 'Allowed to create',
'Allowed to edit' => 'Allowed to edit', 'Allowed to edit' => 'Allowed to edit',
'Allowed to delete' => 'Allowed to delete', 'Allowed to delete' => 'Allowed to delete',
'Administrative panel allowed' => 'Administrative panel allowed', 'Administrative panel allowed' => 'Administrative panel allowed',
'AdminPanel' => 'Administrative panel allowed', 'AdminPanel' => 'Administrative panel allowed',
'Project' => 'Projects',
'Project' => 'Projects',
'ProjectContent' => 'About the project', 'ProjectContent' => 'About the project',
'ProjectLink' => 'Links from the project',
]; ];

View File

@ -292,5 +292,7 @@
'logo' => 'logo', 'logo' => 'logo',
'logo.file' => 'logo file', 'logo.file' => 'logo file',
'logo.delete' => 'remove logo', 'logo.delete' => 'remove logo',
'link' => 'link',
'language_id' => 'language',
], ],
]; ];

View File

@ -249,5 +249,9 @@
"There was an error adding a language": "Произошла ошибка при добавлении языка", "There was an error adding a language": "Произошла ошибка при добавлении языка",
"Select images": "Выберите изображения", "Select images": "Выберите изображения",
"loading": "загрузка", "loading": "загрузка",
"Project information has been successfully updated": "Информация о проекте успешно обновлена" "Project information has been successfully updated": "Информация о проекте успешно обновлена",
"in all languages": "на всех языках",
"The link was successfully created": "Ссылка успешно создана",
"The link was successfully updated": "Ссылка успешно обновлена",
"The link has been deleted": "Ссылка удалена"
} }

View File

@ -10,4 +10,5 @@
'About project' => 'О проекте', 'About project' => 'О проекте',
'Languages' => 'Языки', 'Languages' => 'Языки',
'Last update' => 'Последнее обновление', 'Last update' => 'Последнее обновление',
'Links project' => 'Ссылки от проекта',
]; ];

View File

@ -3,13 +3,16 @@
return [ return [
'Role' => 'Группа пользователей', 'Role' => 'Группа пользователей',
'User' => 'Пользователи', 'User' => 'Пользователи',
'CaptchaToken' => 'Токены для создания капчи',
'Allowed to watch' => 'Разрешено смотреть', 'Allowed to watch' => 'Разрешено смотреть',
'Allowed to create' => 'Разрешено создать', 'Allowed to create' => 'Разрешено создать',
'Allowed to edit' => 'Разрешено редактировать', 'Allowed to edit' => 'Разрешено редактировать',
'Allowed to delete' => 'Разрешено удалять', 'Allowed to delete' => 'Разрешено удалять',
'Administrative panel allowed' => 'Административная панель разрешена', 'Administrative panel allowed' => 'Административная панель разрешена',
'AdminPanel' => 'Административная панель разрешена', 'AdminPanel' => 'Административная панель разрешена',
'Project' => 'Проекты',
'Project' => 'Проекты',
'ProjectContent' => 'О проекте', 'ProjectContent' => 'О проекте',
'ProjectLink' => 'Ссылки от проекта',
]; ];

View File

@ -292,5 +292,7 @@
'logo' => 'логотип', 'logo' => 'логотип',
'logo.file' => 'файл логотипа', 'logo.file' => 'файл логотипа',
'logo.delete' => 'удалить логотип', 'logo.delete' => 'удалить логотип',
'link' => 'ссылка',
'language_id' => 'язык',
], ],
]; ];

View File

@ -0,0 +1,11 @@
@csrf
<x-volt.forms.input :title="__('validation.attributes.title')" name="title" type="text" :value="$link->title" required autofocus />
<x-volt.forms.input :title="__('validation.attributes.link')" name="link" type="text" :value="$link->link" required />
<x-volt.forms.input :title="__('validation.attributes.sort')" name="sort" type="number" :value="$link->sort" required />
<x-volt.forms.select :title="__('validation.attributes.language_id')" name="language_id" :list="$project->languages->pluck('title', 'id')->toArray()" :value="(string) $link->language_id">
<option value="">{{ __('in all languages') }}</option>
</x-volt.forms.select>
@canany(['create', 'update'], $link)
<button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
@endcanany

View File

@ -0,0 +1,8 @@
@can('create', \App\Models\ProjectLink::class)
<div class="mb-4">
<a href="{{ route('admin.projects.links.create', ['project' => $project->id]) }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
<svg class="icon icon-xs me-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
{{ __('Create') }}
</a>
</div>
@endcan

View File

@ -0,0 +1,17 @@
@section('meta_title', __('admin-sections.Links project'))
@section('h1', __('admin-sections.Project') . ': ' . $project->name)
<x-admin.layout>
@include('admin.projects.links._top')
<div class="row">
<div class="col-12 mb-4">
<div class="card border-0 shadow components-section">
<div class="card-body">
<h3 id="category" class="mb-4">{{ __('admin-sections.Links project') }}</h3>
<form method="post" action="{{ route('admin.projects.links.store', ['project' => $project->id]) }}">
@include('admin.projects.links._from')
</form>
</div>
</div>
</div>
</div>
</x-admin.layout>

View File

@ -0,0 +1,18 @@
@section('meta_title', __('admin-sections.Links project'))
@section('h1', __('admin-sections.Project') . ': ' . $project->name)
<x-admin.layout>
@include('admin.projects._top')
<div class="row">
<div class="col-12 mb-4">
<div class="card border-0 shadow components-section">
<div class="card-body">
<h3 id="category" class="mb-4">{{ __('admin-sections.Links project') }}</h3>
<form method="post" action="{{ route('admin.projects.links.update', ['project' => $project->id, 'link' => $link->id]) }}">
@method('PUT')
@include('admin.projects.links._from')
</form>
</div>
</div>
</div>
</div>
</x-admin.layout>

View File

@ -0,0 +1,61 @@
@section('meta_title', __('admin-sections.Links project'))
@section('h1', __('admin-sections.Project') . ': ' . $project->name)
<x-admin.layout>
@include('admin.projects.links._top')
<div class="card border-0 shadow mb-4">
<div class="card-body">
<h3 id="category" class="mb-4">{{ __('admin-sections.Links project') }}</h3>
<div class="table-responsive">
<table class="table table-centered table-nowrap mb-0 rounded">
<thead class="thead-light">
<tr>
<th class="border-0">{{ __('validation.attributes.title') }}</th>
<th class="border-0">{{ __('validation.attributes.link') }}</th>
<th class="border-0">{{ __('validation.attributes.language_id') }}</th>
<th class="border-0 rounded-end" style="width: 150px"></th>
</tr>
</thead>
<tbody>
@foreach($links as $link)
<tr>
<td>
<a href="{{ route('admin.projects.links.edit', ['project' => $project->id, 'link' => $link->id]) }}" class="fw-bold">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="align-text-top" viewBox="0 0 16 16">
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
</svg>
{{ $link->title }}
</a>
</td>
<td>
{{ $link->link }}
</td>
<td>
{{ $link->language?->title ?? __('in all languages') }}
</td>
<td>
@can('delete', $link)
<form method="post" class="d-inline-block" action="{{ route('admin.projects.links.destroy', ['project' => $project->id, 'link' => $link->id]) }}">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger click-confirm" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ __('Delete') }}">
<svg data-slot="icon" fill="currentColor" width="20" height="20" class="align-text-center" 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="M6 18 18 6M6 6l12 12"></path>
</svg>
</button>
</form>
@endcan
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="card-footer border-0">
{{ $links->links() }}
</div>
</div>
</div>
</div>
@push('scripts')
@include('admin._scripts._click-confirm', ['alert' => __('Do you want to delete?')])
@endpush
</x-admin.layout>

View File

@ -15,7 +15,7 @@
@can('viewAny', \App\Models\ProjectContent::class) @can('viewAny', \App\Models\ProjectContent::class)
<tr> <tr>
<td> <td>
<a href="{{ route('admin.projects.about.languages', ['project' => $project->id]) }}" class="fw-bold"> <a href="{{ route('admin.projects.about.languages', ['project' => $project->id]) }}" class="fw-bold">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="align-text-top" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="align-text-top" viewBox="0 0 16 16">
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/> <path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
</svg> </svg>
@ -24,6 +24,18 @@
</td> </td>
</tr> </tr>
@endcan @endcan
@can('viewAny', \App\Models\ProjectLink::class)
<tr>
<td>
<a href="{{ route('admin.projects.links.index', ['project' => $project->id]) }}" class="fw-bold">
<svg data-slot="icon" width="16" height="16" fill="none" stroke-width="1.5" stroke="currentColor" class="align-text-top" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"></path>
</svg>
{{ __('admin-sections.Links project') }}
</a>
</td>
</tr>
@endcan
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -25,6 +25,8 @@
Route::get('about/{language}', [\App\Http\Controllers\Admin\Projects\AboutController::class, 'edit'])->name('about.edit')->where(['language' => '[0-9]+']); Route::get('about/{language}', [\App\Http\Controllers\Admin\Projects\AboutController::class, 'edit'])->name('about.edit')->where(['language' => '[0-9]+']);
Route::put('about/{language}', [\App\Http\Controllers\Admin\Projects\AboutController::class, 'update'])->name('about.update')->where(['language' => '[0-9]+']); Route::put('about/{language}', [\App\Http\Controllers\Admin\Projects\AboutController::class, 'update'])->name('about.update')->where(['language' => '[0-9]+']);
Route::resource('links', \App\Http\Controllers\Admin\Projects\LinksController::class)->except(['show'])->where(['link' => '[0-9]+']);
})->where(['project' => '[0-9]+']); })->where(['project' => '[0-9]+']);
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');
@ -42,4 +44,3 @@
Route::post('image/upload-resize', [\App\Http\Controllers\Storage\ImagesController::class, 'uploadAndResize'])->name('image_upload_and_resize'); Route::post('image/upload-resize', [\App\Http\Controllers\Storage\ImagesController::class, 'uploadAndResize'])->name('image_upload_and_resize');
}); });
}); });