Версия 0.1.0 #1
@ -2,9 +2,16 @@
|
||||
|
||||
namespace App\Dto\Builder;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
final readonly class Project
|
||||
{
|
||||
public function __construct(
|
||||
|
||||
private ?bool $isPublic = null,
|
||||
) { }
|
||||
|
||||
public function isPublic(): ?bool
|
||||
{
|
||||
return $this->isPublic;
|
||||
}
|
||||
}
|
||||
|
16
app/application/app/Enums/CacheTag.php
Normal file
16
app/application/app/Enums/CacheTag.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Illuminate\Cache\TaggedCache;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
enum CacheTag: string
|
||||
{
|
||||
case Project = 'project';
|
||||
|
||||
public function getCache(): TaggedCache
|
||||
{
|
||||
return Cache::tags($this->value);
|
||||
}
|
||||
}
|
31
app/application/app/Enums/Site/ProjectSection.php
Normal file
31
app/application/app/Enums/Site/ProjectSection.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Site;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectLanguage;
|
||||
|
||||
enum ProjectSection
|
||||
{
|
||||
case Home;
|
||||
|
||||
public function url(Project $project, ?ProjectLanguage $language = null): string
|
||||
{
|
||||
$parameters = [];
|
||||
$prefixProject = '';
|
||||
if ($project->http_host === null) {
|
||||
$prefixProject = 'project.';
|
||||
$parameters['project'] = $project->code;
|
||||
}
|
||||
|
||||
$prefixLanguage = '';
|
||||
if ($language?->is_default === false) {
|
||||
$parameters['language'] = $language->code;
|
||||
$prefixLanguage = '-language';
|
||||
}
|
||||
|
||||
return match ($this) {
|
||||
self::Home => \route($prefixProject . 'home' . $prefixLanguage, $parameters),
|
||||
};
|
||||
}
|
||||
}
|
18
app/application/app/Http/Controllers/Site/Controller.php
Normal file
18
app/application/app/Http/Controllers/Site/Controller.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Site;
|
||||
|
||||
use App\Http\Controllers\Controller as BaseController;
|
||||
use app\ServiceResults\Site\PagePossibleWithoutTranslation;
|
||||
use Illuminate\View\View;
|
||||
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
protected function viewPageWithoutTranslation(PagePossibleWithoutTranslation $result): View
|
||||
{
|
||||
return \view('site.page-without-translation', [
|
||||
'project' => $result->getProject(),
|
||||
'language' => $result->getLanguage(),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Site;
|
||||
|
||||
use App\Enums\Site\ProjectSection;
|
||||
use App\Services\Site\ProjectService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class ProjectsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProjectService $projectService,
|
||||
) { }
|
||||
|
||||
public function index(Request $request, ?string $language = null): View
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (\is_null($request->project)) {
|
||||
if (!\is_null($language)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$with = ['storage'];
|
||||
$result = $this->projectService->getProjects($user, $with);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return \view('site.projects.index', $result->getData());
|
||||
}
|
||||
|
||||
$result = $this->projectService->getAboutByProject($request->project, $language, $request->user());
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
if ($result->isTranslation()) {
|
||||
return $this->viewPageWithoutTranslation($result);
|
||||
}
|
||||
|
||||
return \view('site.projects.about', $result->getData());
|
||||
}
|
||||
|
||||
public function about(string $project, Request $request, ?string $language = null)
|
||||
{
|
||||
$result = $this->projectService->getAbout($project, $language, $request->user());
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
if ($result->isTranslation()) {
|
||||
return $this->viewPageWithoutTranslation($result);
|
||||
}
|
||||
|
||||
if ($result->getProject()->http_href !== null) {
|
||||
$link = ProjectSection::Home->url($result->getProject(), $result->getLanguage());
|
||||
return \redirect($result->getProject()->http_href . $link, 302);
|
||||
}
|
||||
|
||||
return \view('site.projects.about', $result->getData());
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AdminPanel
|
||||
final class AdminPanel
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
|
50
app/application/app/Http/Middleware/Project.php
Normal file
50
app/application/app/Http/Middleware/Project.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Enums\CacheTag;
|
||||
use App\Repositories\ProjectRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Closure;
|
||||
|
||||
class Project
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProjectRepository $projectRepository,
|
||||
) { }
|
||||
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$projectCode = $request->route()?->parameter('project');
|
||||
if ($projectCode === null) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$seconds = 3600;
|
||||
$project = CacheTag::Project->getCache()->remember(self::class . $projectCode, $seconds, function () use ($projectCode) {
|
||||
return $this->projectRepository->getProjectByCode($projectCode) ?? false;
|
||||
});
|
||||
if ($project === false) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
unset($request->route()->parameters['project']);
|
||||
|
||||
$routeName = $request->route()->getName();
|
||||
if (
|
||||
$routeName !== null
|
||||
&& $project->http_host !== null
|
||||
&& $project->http_host !== $request->getSchemeAndHttpHost()
|
||||
) {
|
||||
$routeName = Str::of($routeName)->replaceStart('project.', '')->toString();
|
||||
$route = \route($routeName, $request->route()->parameters(), false);
|
||||
return redirect($project->http_host . $route, 302);
|
||||
}
|
||||
|
||||
$request->merge(['project' => $project]);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
32
app/application/app/Http/Middleware/ProjectDomain.php
Normal file
32
app/application/app/Http/Middleware/ProjectDomain.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Enums\CacheTag;
|
||||
use App\Repositories\ProjectRepository;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class ProjectDomain
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProjectRepository $projectRepository,
|
||||
) { }
|
||||
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$httpHost = $request->getSchemeAndHttpHost();
|
||||
|
||||
$seconds = 3600;
|
||||
$project = CacheTag::Project->getCache()->remember(self::class . $httpHost, $seconds, function () use ($httpHost) {
|
||||
return $this->projectRepository->getProjectByHttpHost($httpHost) ?? false;
|
||||
});
|
||||
if ($project === false) {
|
||||
$project = null;
|
||||
}
|
||||
$request->merge(['project' => $project]);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -18,10 +18,12 @@ class StoreUpdateRequest extends FormRequest implements FormRequestDto
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$projectId = $this->project ?? null;
|
||||
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'code' => ['required', 'string', 'min:3', 'max:255', 'regex:/^[a-z0-9_-]+$/i'],
|
||||
'http_host' => ['nullable', 'string', 'max:255', new HttpHost()],
|
||||
'code' => ['required', 'string', 'min:3', 'max:255', 'regex:/^[a-z0-9_-]+$/'],
|
||||
'http_host' => ['nullable', 'string', 'max:255', new HttpHost(), 'unique:projects,http_host,' . $projectId],
|
||||
'is_public' => ['required', 'boolean'],
|
||||
|
||||
'logo.file' => ['nullable', 'numeric', 'min:1'],
|
||||
@ -29,7 +31,7 @@ public function rules(): array
|
||||
|
||||
'languages.items.*.id' => ['nullable', 'numeric'],
|
||||
'languages.items.*.title' => ['required', 'string', 'max:255'],
|
||||
'languages.items.*.code' => ['required', 'string', 'min:2', 'max:30', 'regex:/^[a-zA-Z_]+$/i'],
|
||||
'languages.items.*.code' => ['required', 'string', 'min:2', 'max:30', 'regex:/^[a-z_]+$/'],
|
||||
'languages.items.*.sort' => ['required', 'numeric', 'min:-1000', 'max:1000'],
|
||||
'languages.default' => ['required', 'numeric', function (string $attribute, mixed $value, \Closure $fail) {
|
||||
$languages = $this->input('languages.items', []);
|
||||
|
@ -12,6 +12,7 @@
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
@ -56,6 +57,9 @@ public function boot(): void
|
||||
|
||||
Relation::enforceMorphMap(Morph::map());
|
||||
|
||||
Route::pattern('language', '[a-z_]+');
|
||||
Route::pattern('project', '[a-z0-9_-]+');
|
||||
|
||||
$this->configureRateLimiting();
|
||||
Gate::define('AdminPanel', [\App\Policies\AdminPanel::class, 'view']);
|
||||
}
|
||||
|
@ -2,10 +2,20 @@
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectLanguage;
|
||||
|
||||
final class ProjectLanguageRepository
|
||||
{
|
||||
public function getProjectLanguageByCodeOrDefault(Project $project, ?string $code): ?ProjectLanguage
|
||||
{
|
||||
if (\is_null($code)) {
|
||||
return $project->languages()->where('is_default', true)->first();
|
||||
}
|
||||
|
||||
return $project->languages()->where('code', $code)->first();
|
||||
}
|
||||
|
||||
public function isExistsLanguageById(int $projectId, int $languageId): bool
|
||||
{
|
||||
return ProjectLanguage::query()
|
||||
|
@ -3,10 +3,13 @@
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Contracts\Search;
|
||||
use App\Models\Project;
|
||||
use App\Services\ProjectLink\BuilderCommand;
|
||||
use App\Services\Search\CreateSearchInstanceCommand;
|
||||
use App\Dto\Builder\ProjectLink as ProjectLinkBuilderDto;
|
||||
use App\Models\ProjectLink;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
final readonly class ProjectLinkRepository
|
||||
{
|
||||
@ -29,4 +32,11 @@ public function getLinkById(int $id): ?ProjectLink
|
||||
{
|
||||
return ProjectLink::query()->where('id', $id)->first();
|
||||
}
|
||||
|
||||
public function getLinksByProject(Project $project, int $languageId): Collection
|
||||
{
|
||||
return $project->links()->where(function (Builder $query) use ($languageId) {
|
||||
$query->whereNull('language_id')->orWhere('language_id', $languageId);
|
||||
})->get();
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,11 @@ public function getProjects(ProjectBuilderDto $projectBuilderDto, array $with =
|
||||
return $this->createSearchInstanceCommand->execute($query);
|
||||
}
|
||||
|
||||
public function getProjectByHttpHost(string $httpHost): ?Project
|
||||
{
|
||||
return Project::query()->where('http_host', $httpHost)->first();
|
||||
}
|
||||
|
||||
public function getProjectById(int $id): ?Project
|
||||
{
|
||||
return Project::query()->where('id', $id)->first();
|
||||
|
@ -0,0 +1,37 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace app\ServiceResults\Site;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectLanguage;
|
||||
use App\ServiceResults\ServiceResult;
|
||||
|
||||
final class PagePossibleWithoutTranslation extends ServiceResult
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Project $project,
|
||||
private readonly ProjectLanguage $language,
|
||||
private readonly array $data,
|
||||
private readonly bool $isTranslation,
|
||||
) { }
|
||||
|
||||
public function isTranslation(): bool
|
||||
{
|
||||
return $this->isTranslation;
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function getProject(): Project
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
public function getLanguage(): ProjectLanguage
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
use App\Dto\Builder\Project as ProjectBuilderDto;
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Dto\Service\Admin\Project\StoreUpdate;
|
||||
use App\Enums\CacheTag;
|
||||
use App\Enums\Morph;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectLanguage;
|
||||
@ -14,6 +15,7 @@
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use App\ServiceResults\StoreUpdateResult;
|
||||
use App\Services\ClearCacheCommandHandler;
|
||||
use App\Services\Project\ProjectCommandHandler;
|
||||
use App\Services\ProjectLanguage\ModelSyncCommand;
|
||||
use App\Services\Role\CreateAdminRoleForProjectCommand;
|
||||
@ -29,6 +31,7 @@ public function __construct(
|
||||
private readonly CreateAdminRoleForProjectCommand $createAdminRoleForProjectCommand,
|
||||
private readonly ModelSyncCommand $languageModelSyncCommand,
|
||||
private readonly StorageService $storageService,
|
||||
private readonly ClearCacheCommandHandler $clearCacheCommandHandler,
|
||||
) { }
|
||||
|
||||
public function index(ProjectBuilderDto $projectBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
|
||||
@ -130,6 +133,7 @@ public function store(StoreUpdate $data, User $user): ServiceResultError | Store
|
||||
|
||||
return $project;
|
||||
});
|
||||
$this->clearCacheCommandHandler->byTag(CacheTag::Project);
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
@ -172,6 +176,7 @@ public function update(int $id, StoreUpdate $data, User $user): ServiceResultErr
|
||||
|
||||
return $project;
|
||||
});
|
||||
$this->clearCacheCommandHandler->byTag(CacheTag::Project);
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
@ -196,6 +201,7 @@ public function destroy(int $id, User $user): ServiceResultError|ServiceResultSu
|
||||
DB::transaction(function () use ($project) {
|
||||
$this->projectCommandHandler->handleDestroy($project);
|
||||
});
|
||||
$this->clearCacheCommandHandler->byTag(CacheTag::Project);
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
|
13
app/application/app/Services/ClearCacheCommandHandler.php
Normal file
13
app/application/app/Services/ClearCacheCommandHandler.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\CacheTag;
|
||||
|
||||
final readonly class ClearCacheCommandHandler
|
||||
{
|
||||
public function byTag(CacheTag $tag)
|
||||
{
|
||||
$tag->getCache()->flush();
|
||||
}
|
||||
}
|
@ -10,6 +10,10 @@
|
||||
{
|
||||
public function execute(Relation | Builder $query, ProjectBuilderDto $projectBuilderDto): Relation | Builder
|
||||
{
|
||||
if ($projectBuilderDto->isPublic() !== null) {
|
||||
$query->where('is_public', $projectBuilderDto->isPublic());
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,16 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectLanguage;
|
||||
use App\ServiceResults\ServiceResultArray;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use App\ServiceResults\Site\PagePossibleWithoutTranslation;
|
||||
use App\ServiceResults\StoreUpdateResult;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
abstract class Service
|
||||
{
|
||||
@ -56,6 +60,11 @@ final protected function result(array $data = []): ServiceResultArray
|
||||
return new ServiceResultArray(data: $data);
|
||||
}
|
||||
|
||||
final protected function resultSitePage(Project $project, ProjectLanguage $language, array $data, bool $isTranslation): PagePossibleWithoutTranslation
|
||||
{
|
||||
return new PagePossibleWithoutTranslation($project, $language, $data, $isTranslation);
|
||||
}
|
||||
|
||||
final protected function error(int $code, string $message, array $errors = []): ServiceResultError
|
||||
{
|
||||
return new ServiceResultError(
|
||||
|
73
app/application/app/Services/Site/ProjectService.php
Normal file
73
app/application/app/Services/Site/ProjectService.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Site;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use App\Dto\Builder\Project as ProjectBuilderDto;
|
||||
use App\Repositories\ProjectContentRepository;
|
||||
use App\Repositories\ProjectLanguageRepository;
|
||||
use App\Repositories\ProjectLinkRepository;
|
||||
use App\Repositories\ProjectRepository;
|
||||
use App\ServiceResults\ServiceResultArray;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\Site\PagePossibleWithoutTranslation;
|
||||
use App\Services\Service;
|
||||
|
||||
final class ProjectService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProjectRepository $projectRepository,
|
||||
private readonly ProjectLanguageRepository $projectLanguageRepository,
|
||||
private readonly ProjectContentRepository $projectContentRepository,
|
||||
private readonly ProjectLinkRepository $projectLinkRepository,
|
||||
) { }
|
||||
|
||||
public function getProjects(?User $user, array $with): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
$isPublic = null;
|
||||
if (\is_null($user) || $user->cannot('viewAny', Project::class)) {
|
||||
$isPublic = true;
|
||||
}
|
||||
$projectBuilderDto = new ProjectBuilderDto(
|
||||
isPublic: $isPublic,
|
||||
);
|
||||
$projects = $this->projectRepository->getProjects($projectBuilderDto, $with);
|
||||
|
||||
return $this->result([
|
||||
'projects' => $projects->all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getAbout(string $projectCode, ?string $languageCode, ?User $user = null): ServiceResultError | PagePossibleWithoutTranslation
|
||||
{
|
||||
$project = $this->projectRepository->getProjectByCode($projectCode);
|
||||
if (\is_null($project)) {
|
||||
return $this->errNotFound('Project not found');
|
||||
}
|
||||
|
||||
return $this->getAboutByProject($project, $languageCode, $user);
|
||||
}
|
||||
|
||||
public function getAboutByProject(Project $project, ?string $languageCode, ?User $user = null): ServiceResultError | PagePossibleWithoutTranslation
|
||||
{
|
||||
if (
|
||||
$project->is_public === false
|
||||
&& ( $user === null || $user->cannot('view', $project) )
|
||||
) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
$language = $this->projectLanguageRepository->getProjectLanguageByCodeOrDefault($project, $languageCode);
|
||||
if (!$language) {
|
||||
return $this->errNotFound(__('Language not found'));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'project' => $project,
|
||||
'language' => $language,
|
||||
'content' => $this->projectContentRepository->getContentByLanguageId($project->id, $language->id),
|
||||
'links' => $this->projectLinkRepository->getLinksByProject($project, $language->id),
|
||||
];
|
||||
return $this->resultSitePage($project, $language, $data, \is_null($data['content']));
|
||||
}
|
||||
}
|
14
app/application/app/View/Components/HomeLayout.php
Normal file
14
app/application/app/View/Components/HomeLayout.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace app\View\Components;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class HomeLayout extends Component
|
||||
{
|
||||
public function render(): View
|
||||
{
|
||||
return view('layout.home');
|
||||
}
|
||||
}
|
31
app/application/app/View/Components/Site/ChooseLanguage.php
Normal file
31
app/application/app/View/Components/Site/ChooseLanguage.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace app\View\Components\Site;
|
||||
|
||||
use App\Models\ProjectLanguage;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class ChooseLanguage extends Component
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProjectLanguage $language,
|
||||
private readonly Collection $languages,
|
||||
) { }
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$link = Str::of( request()->getRequestUri() )->rtrim('/');
|
||||
if ($link->endsWith('/language/' . $this->language->code)) {
|
||||
$link = $link->replace('/language/' . $this->language->code, '', false);
|
||||
}
|
||||
|
||||
return view('components.site.choose-language', [
|
||||
'language' => $this->language,
|
||||
'languages' => $this->languages,
|
||||
'link' => (string) $link,
|
||||
]);
|
||||
}
|
||||
}
|
26
app/application/app/View/Components/Site/Layout.php
Normal file
26
app/application/app/View/Components/Site/Layout.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace app\View\Components\Site;
|
||||
|
||||
use App\Enums\StorageType;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectLanguage;
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class Layout extends Component
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Project $project,
|
||||
private readonly ProjectLanguage $language,
|
||||
) { }
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('layout.site', [
|
||||
'project' => $this->project,
|
||||
'logo' => $this->project->getStorageOne(StorageType::Logo),
|
||||
'language' => $this->language,
|
||||
]);
|
||||
}
|
||||
}
|
@ -3,12 +3,23 @@
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
then: function () {
|
||||
Route::middleware([\App\Http\Middleware\ProjectDomain::class])->group(base_path('routes/web-project.php'));
|
||||
Route::middleware([
|
||||
\App\Http\Middleware\ProjectDomain::class,
|
||||
\App\Http\Middleware\Project::class,
|
||||
])
|
||||
->prefix('project/{project}')
|
||||
->as('project.')
|
||||
->group(base_path('routes/web-project.php'));
|
||||
},
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
//
|
||||
|
@ -254,4 +254,6 @@
|
||||
"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",
|
||||
"Language not found": "Language not found",
|
||||
"Project not found": "Project not found"
|
||||
}
|
||||
|
10
app/application/lang/en/site.php
Normal file
10
app/application/lang/en/site.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
return [
|
||||
"Menu" => "Menu",
|
||||
'Projects' => 'Projects',
|
||||
'Project' => 'Project',
|
||||
'About project' => 'About the project',
|
||||
'Page without translation' => 'Page without translation',
|
||||
'Powered by service' => 'Powered by the My Projects website engine',
|
||||
'Choose language' => 'Choose language',
|
||||
];
|
@ -253,5 +253,7 @@
|
||||
"in all languages": "на всех языках",
|
||||
"The link was successfully created": "Ссылка успешно создана",
|
||||
"The link was successfully updated": "Ссылка успешно обновлена",
|
||||
"The link has been deleted": "Ссылка удалена"
|
||||
"The link has been deleted": "Ссылка удалена",
|
||||
"Language not found": "Язык не найден",
|
||||
"Project not found": "Проект не найден"
|
||||
}
|
||||
|
10
app/application/lang/ru/site.php
Normal file
10
app/application/lang/ru/site.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
return [
|
||||
"Menu" => "Меню",
|
||||
'Projects' => 'Проекты',
|
||||
'Project' => 'Проект',
|
||||
'About project' => 'О проекте',
|
||||
'Page without translation' => 'Страница без перевода',
|
||||
'Powered by service' => 'Работает на движке сайта «Мои проекты»',
|
||||
'Choose language' => 'Выберите язык',
|
||||
];
|
72
app/application/resources/home/scss/app.scss
Normal file
72
app/application/resources/home/scss/app.scss
Normal file
@ -0,0 +1,72 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
|
||||
@import "reset";
|
||||
|
||||
body {
|
||||
margin: 0 20px;
|
||||
font-family: "Roboto", sans-serif;
|
||||
}
|
||||
|
||||
.projects-list {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover .projects-list__title {
|
||||
box-shadow: 0px -1px 4px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
li {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
.projects-list__img {
|
||||
display: block;
|
||||
background: #ddd;
|
||||
height: 150px;
|
||||
img {
|
||||
object-fit: contain;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.projects-list__title {
|
||||
background: #ccc;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
padding: 10px;
|
||||
box-shadow: 0px -1px 4px rgba(0, 0, 0, 0.3);
|
||||
transition: box-shadow 0.3s ease-out;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.projects-list {
|
||||
display: grid;
|
||||
margin-left: -20px;
|
||||
li {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 600px) and (max-width: 899px) {
|
||||
.projects-list {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
@media (min-width: 900px) and (max-width: 1100px) {
|
||||
.projects-list {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1001px) {
|
||||
body {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.projects-list {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
73
app/application/resources/home/scss/reset.scss
Normal file
73
app/application/resources/home/scss/reset.scss
Normal file
@ -0,0 +1,73 @@
|
||||
/* Box sizing rules */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Prevent font size inflation */
|
||||
html {
|
||||
-moz-text-size-adjust: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
/* Remove default margin in favour of better control in authored CSS */
|
||||
body, h1, h2, h3, h4, p,
|
||||
figure, blockquote, dl, dd {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
|
||||
ul[role='list'],
|
||||
ol[role='list'] {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* Set core body defaults */
|
||||
body {
|
||||
min-height: 100vh;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Set shorter line heights on headings and interactive elements */
|
||||
h1, h2, h3, h4,
|
||||
button, input, label {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* Balance text wrapping on headings */
|
||||
h1, h2,
|
||||
h3, h4 {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* A elements that don't have a class get default styles */
|
||||
a:not([class]) {
|
||||
text-decoration-skip-ink: auto;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
/* Make images easier to work with */
|
||||
img,
|
||||
picture {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Inherit fonts for inputs and buttons */
|
||||
input, button,
|
||||
textarea, select {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
/* Make sure textareas without a rows attribute are not tiny */
|
||||
textarea:not([rows]) {
|
||||
min-height: 10em;
|
||||
}
|
||||
|
||||
/* Anything that has been anchored to should have extra scroll margin */
|
||||
:target {
|
||||
scroll-margin-block: 5ex;
|
||||
}
|
8
app/application/resources/site/js/_choose-language.js
Normal file
8
app/application/resources/site/js/_choose-language.js
Normal file
@ -0,0 +1,8 @@
|
||||
let blockLanguage = document.querySelector('#language');
|
||||
blockLanguage.querySelector('.language__button').addEventListener('click', (e) => {
|
||||
if (blockLanguage.classList.contains('active')) {
|
||||
blockLanguage.classList.remove('active');
|
||||
} else {
|
||||
blockLanguage.classList.add('active');
|
||||
}
|
||||
});
|
8
app/application/resources/site/js/_menu.js
Normal file
8
app/application/resources/site/js/_menu.js
Normal file
@ -0,0 +1,8 @@
|
||||
let body = document.querySelector('body');
|
||||
document.querySelector('#mobile-menu').addEventListener('click', (e) => {
|
||||
if (body.classList.contains('mobile-menu-open')) {
|
||||
body.classList.remove('mobile-menu-open');
|
||||
} else {
|
||||
body.classList.add('mobile-menu-open');
|
||||
}
|
||||
});
|
2
app/application/resources/site/js/app.js
Normal file
2
app/application/resources/site/js/app.js
Normal file
@ -0,0 +1,2 @@
|
||||
import './_menu.js';
|
||||
import './_choose-language.js';
|
252
app/application/resources/site/scss/app.scss
Normal file
252
app/application/resources/site/scss/app.scss
Normal file
@ -0,0 +1,252 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;1,400;1,700&display=swap');
|
||||
@import "reset";
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Roboto", sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
|
||||
.main-container {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.section-container {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
min-height: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
.content {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-areas:
|
||||
"logo menu"
|
||||
"language language";
|
||||
padding: 8px;
|
||||
box-shadow: 0 3px 9px rgba(0,0,0,0.48);
|
||||
background: #eee;
|
||||
}
|
||||
.header_logo {
|
||||
grid-area: logo;
|
||||
max-height: 50px;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
img {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #0a0e17;
|
||||
font-size: 20px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #0a0e17;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
#mobile-menu {
|
||||
grid-area: menu;
|
||||
border: 0;
|
||||
background: none;
|
||||
color: #FF2D20;
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.close {
|
||||
display: none;
|
||||
margin-top: -5px;
|
||||
}
|
||||
}
|
||||
#menu {
|
||||
display: none;
|
||||
|
||||
ul {
|
||||
padding: 10px 0 0 20px;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
color: #0a0e17;
|
||||
}
|
||||
|
||||
a.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.menu__title {
|
||||
background: #ccc;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
padding: 12px 0 12px 20px;
|
||||
box-shadow: 0 3px 9px rgba(0,0,0,0.48);
|
||||
}
|
||||
body.mobile-menu-open {
|
||||
#mobile-menu {
|
||||
.open {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.close {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
#menu {
|
||||
background: #ddd;
|
||||
display: block;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
#language {
|
||||
grid-area: language;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.language__block {
|
||||
position: relative;
|
||||
}
|
||||
.language__button {
|
||||
border: 0;
|
||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.48);
|
||||
background: #ccc;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
color: #000;
|
||||
}
|
||||
.language__button__str {
|
||||
position: absolute;
|
||||
top: calc(50% - 8px);
|
||||
right: 10px;
|
||||
}
|
||||
.language__list {
|
||||
background: #ddd;
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
list-style: none;
|
||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.48);
|
||||
padding: 10px 0;
|
||||
margin: 0;
|
||||
border-radius: 0 0 5px 5px;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
}
|
||||
#language.active {
|
||||
.language__list {
|
||||
display: block;
|
||||
}
|
||||
.language__button {
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
}
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.content {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #eee;
|
||||
padding: 15px 0 15px 15px;
|
||||
box-shadow: 5px 0px 5px rgba(0,0,0,0.48);
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #0a0e17;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
body {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header {
|
||||
grid-template-areas: "logo language";
|
||||
grid-template-columns: 1fr 200px;
|
||||
}
|
||||
#mobile-menu {
|
||||
display: none;
|
||||
}
|
||||
.main-container {
|
||||
flex-direction: row;
|
||||
}
|
||||
.language__button {
|
||||
padding: 7px 10px 7px 0;
|
||||
}
|
||||
#menu {
|
||||
background: #eee;
|
||||
display: block;
|
||||
width: 175px;
|
||||
box-shadow: 0px 7px 8px rgba(0,0,0,0.48);
|
||||
}
|
||||
.menu__title {
|
||||
display: none;
|
||||
}
|
||||
}
|
73
app/application/resources/site/scss/reset.scss
Normal file
73
app/application/resources/site/scss/reset.scss
Normal file
@ -0,0 +1,73 @@
|
||||
/* Box sizing rules */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Prevent font size inflation */
|
||||
html {
|
||||
-moz-text-size-adjust: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
/* Remove default margin in favour of better control in authored CSS */
|
||||
body, h1, h2, h3, h4, p,
|
||||
figure, blockquote, dl, dd {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
|
||||
ul[role='list'],
|
||||
ol[role='list'] {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* Set core body defaults */
|
||||
body {
|
||||
min-height: 100vh;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Set shorter line heights on headings and interactive elements */
|
||||
h1, h2, h3, h4,
|
||||
button, input, label {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* Balance text wrapping on headings */
|
||||
h1, h2,
|
||||
h3, h4 {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* A elements that don't have a class get default styles */
|
||||
a:not([class]) {
|
||||
text-decoration-skip-ink: auto;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
/* Make images easier to work with */
|
||||
img,
|
||||
picture {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Inherit fonts for inputs and buttons */
|
||||
input, button,
|
||||
textarea, select {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
/* Make sure textareas without a rows attribute are not tiny */
|
||||
textarea:not([rows]) {
|
||||
min-height: 10em;
|
||||
}
|
||||
|
||||
/* Anything that has been anchored to should have extra scroll margin */
|
||||
:target {
|
||||
scroll-margin-block: 5ex;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<div id="language">
|
||||
<div class="language__block">
|
||||
<button class="language__button" type="button" aria-label="{{ __('site.Choose language') }}">
|
||||
<svg width="16" height="16" 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="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802"></path>
|
||||
</svg>
|
||||
{{ $language->title }}
|
||||
<svg width="16" height="16" class="language__button__str" 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="m19.5 8.25-7.5 7.5-7.5-7.5"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<ul class="language__list">
|
||||
@foreach($languages as $lang)
|
||||
@php
|
||||
$code = '';
|
||||
if ($lang->is_default !== true) {
|
||||
$code = '/language/' . $lang->code;
|
||||
} else if($link === '') {
|
||||
$code = '/';
|
||||
}
|
||||
@endphp
|
||||
<li><a href="{{ $link . $code }}">{{ $lang->title }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
16
app/application/resources/views/layout/home.blade.php
Normal file
16
app/application/resources/views/layout/home.blade.php
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>@yield('meta_title', '')</title>
|
||||
<meta name="keywords" content="@yield('meta_keywords', '')" />
|
||||
<meta name="description" content="@yield('meta_description', '')" />
|
||||
|
||||
@vite('resources/home/scss/app.scss')
|
||||
</head>
|
||||
<body>
|
||||
{{ $slot }}
|
||||
</body>
|
||||
</html>
|
55
app/application/resources/views/layout/site.blade.php
Normal file
55
app/application/resources/views/layout/site.blade.php
Normal file
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ $language->code }}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>@yield('meta_title', '')</title>
|
||||
<meta name="keywords" content="@yield('meta_keywords', '')" />
|
||||
<meta name="description" content="@yield('meta_description', '')" />
|
||||
|
||||
@vite('resources/site/scss/app.scss')
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<header class="header">
|
||||
<div class="header_logo">
|
||||
<a href="{{ \App\Enums\Site\ProjectSection::Home->url($project, $language) }}">
|
||||
@if($logo)
|
||||
<img src="{{ $logo->url }}" alt="{{ $project->name }}">
|
||||
@else
|
||||
<strong>{{ $project->name }}</strong>
|
||||
@endif
|
||||
</a>
|
||||
</div>
|
||||
<button id="mobile-menu" type="button" aria-label="{{ __('site.Menu') }}">
|
||||
<svg class="open" width="45" height="45" 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="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"></path>
|
||||
</svg>
|
||||
<svg class="close" width="45" height="45" 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="M6 18 18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<x-site.choose-language :language="$language" :languages="$project->languages" />
|
||||
</header>
|
||||
<div class="main-container">
|
||||
<nav id="menu">
|
||||
<div class="menu__title">{{ __('site.Menu') }}</div>
|
||||
<ul>
|
||||
<li><a href="{{ \App\Enums\Site\ProjectSection::Home->url($project, $language) }}" @class(['active' => request()->route()->named(['home', 'language.home'])])>{{ __('site.About project') }}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="section-container">
|
||||
<div class="content">
|
||||
<h1>@yield('h1', '')</h1>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<a href="https://git.kor-elf.net/kor-elf/my-projects-website" target="_blank">{{ __('site.Powered by service') }}</a>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@vite('resources/site/js/app.js')
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,5 @@
|
||||
@section('h1', __('site.Page without translation'))
|
||||
|
||||
<x-site.layout :project="$project" :language="$language">
|
||||
|
||||
</x-site.layout>
|
@ -0,0 +1,6 @@
|
||||
@section('meta_title', __('site.Project') . ': ' . $content->title)
|
||||
@section('h1', $content->title)
|
||||
|
||||
<x-site.layout :project="$project" :language="$language">
|
||||
{!! $content->description !!}
|
||||
</x-site.layout>
|
@ -0,0 +1,18 @@
|
||||
@section('meta_title', __('site.Projects'))
|
||||
|
||||
<x-home-layout>
|
||||
<h1>{{ __('site.Projects') }}</h1>
|
||||
<ul class="projects-list">
|
||||
@foreach($projects as $project)
|
||||
@php
|
||||
$logo = $project->getStorageOne(\App\Enums\StorageType::Logo);
|
||||
@endphp
|
||||
<li>
|
||||
<a href="{{ \App\Enums\Site\ProjectSection::Home->url($project) }}">
|
||||
<span class="projects-list__img">@if($logo)<img src="{{ $logo->url }}" alt="{{ $project->name }}">@endif</span>
|
||||
<span class="projects-list__title">{{ $project->name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</x-home-layout>
|
6
app/application/routes/web-project.php
Normal file
6
app/application/routes/web-project.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
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');
|
@ -8,6 +8,11 @@ export default defineConfig({
|
||||
input: [
|
||||
'resources/volt/scss/app.scss',
|
||||
'resources/volt/js/app.js',
|
||||
|
||||
'resources/home/scss/app.scss',
|
||||
|
||||
'resources/site/scss/app.scss',
|
||||
'resources/site/js/app.js',
|
||||
],
|
||||
refresh: true,
|
||||
}),
|
||||
|
Loading…
Reference in New Issue
Block a user