Proje dosyaları eklendi
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
<x-layouts.app :title="__('ui.forgot_password').' — TrafficPumper'"><x-auth-card :title="__('ui.forgot_password')" :description="__('ui.forgot_description')"><form method="POST" action="{{ route('password.email') }}">@csrf
|
||||
<div class="mb-3"><label class="form-label" for="email">{{ __('ui.email') }}</label><input class="form-control" id="email" name="email" type="email" value="{{ old('email') }}" required autofocus></div><button class="btn btn-primary w-100" type="submit">{{ __('ui.send_link') }}</button></form></x-auth-card></x-layouts.app>
|
||||
@@ -0,0 +1,8 @@
|
||||
<x-layouts.app :title="__('ui.sign_in').' — TrafficPumper'"><x-auth-card :title="__('ui.sign_in')" :description="__('ui.login_description')">
|
||||
<form method="POST" action="{{ route('login') }}">@csrf
|
||||
<div class="mb-3"><label class="form-label" for="email">{{ __('ui.email') }}</label><input class="form-control" id="email" name="email" type="email" value="{{ old('email') }}" required autofocus autocomplete="email"></div>
|
||||
<div class="mb-3"><label class="form-label" for="password">{{ __('ui.password') }}</label><input class="form-control" id="password" name="password" type="password" required autocomplete="current-password"></div>
|
||||
<div class="form-check mb-3"><input class="form-check-input" id="remember" name="remember" type="checkbox"><label class="form-check-label" for="remember">{{ __('ui.remember') }}</label></div>
|
||||
<button class="btn btn-primary w-100" type="submit">{{ __('ui.sign_in') }}</button></form>
|
||||
<div class="tp-auth-links d-flex flex-wrap justify-content-between gap-2 mt-3 small"><a href="{{ route('password.request') }}">{{ __('ui.forgot_password') }}</a><a href="{{ route('register') }}">{{ __('ui.register') }}</a></div>
|
||||
</x-auth-card></x-layouts.app>
|
||||
@@ -0,0 +1,6 @@
|
||||
<x-layouts.app :title="__('ui.register').' — TrafficPumper'"><x-auth-card :title="__('ui.create_account')" :description="__('ui.register_description')"><form method="POST" action="{{ route('register') }}">@csrf
|
||||
<div class="mb-3"><label class="form-label" for="name">{{ __('ui.full_name') }}</label><input class="form-control" id="name" name="name" value="{{ old('name') }}" required autofocus autocomplete="name"></div>
|
||||
<div class="mb-3"><label class="form-label" for="email">{{ __('ui.email') }}</label><input class="form-control" id="email" name="email" type="email" value="{{ old('email') }}" required autocomplete="email"></div>
|
||||
<div class="mb-3"><label class="form-label" for="password">{{ __('ui.password') }}</label><input class="form-control" id="password" name="password" type="password" required autocomplete="new-password"></div>
|
||||
<div class="mb-3"><label class="form-label" for="password_confirmation">{{ __('ui.password_confirmation') }}</label><input class="form-control" id="password_confirmation" name="password_confirmation" type="password" required autocomplete="new-password"></div>
|
||||
<button class="btn btn-primary w-100" type="submit">{{ __('ui.register') }}</button></form></x-auth-card></x-layouts.app>
|
||||
@@ -0,0 +1,4 @@
|
||||
<x-layouts.app :title="__('ui.reset_password').' — TrafficPumper'"><x-auth-card :title="__('ui.reset_password')"><form method="POST" action="{{ route('password.store') }}">@csrf<input type="hidden" name="token" value="{{ $request->route('token') }}">
|
||||
<div class="mb-3"><label class="form-label" for="email">{{ __('ui.email') }}</label><input class="form-control" id="email" name="email" type="email" value="{{ old('email', $request->email) }}" required></div>
|
||||
<div class="mb-3"><label class="form-label" for="password">{{ __('ui.new_password') }}</label><input class="form-control" id="password" name="password" type="password" required autocomplete="new-password"></div>
|
||||
<div class="mb-3"><label class="form-label" for="password_confirmation">{{ __('ui.password_confirmation') }}</label><input class="form-control" id="password_confirmation" name="password_confirmation" type="password" required autocomplete="new-password"></div><button class="btn btn-primary w-100" type="submit">{{ __('ui.renew_password') }}</button></form></x-auth-card></x-layouts.app>
|
||||
@@ -0,0 +1 @@
|
||||
<x-layouts.app :title="__('ui.verify_email').' — TrafficPumper'"><x-auth-card :title="__('ui.verify_heading')" :description="__('ui.verify_description')"><form method="POST" action="{{ route('verification.send') }}">@csrf<button class="btn btn-primary w-100" type="submit">{{ __('ui.resend_verification') }}</button></form></x-auth-card></x-layouts.app>
|
||||
@@ -0,0 +1,12 @@
|
||||
@props(['title', 'description' => null])
|
||||
<main class="container tp-auth-page py-4 py-md-5">
|
||||
<div class="row g-0 justify-content-center"><div class="col-12 col-md-8 col-lg-5 tp-auth-column">
|
||||
<div class="card border-0 shadow-sm tp-auth-card"><div class="card-body p-3 p-sm-4 p-md-5">
|
||||
<div class="d-flex justify-content-center mb-4"><x-brand-logo :href="route('home')" height="76px" class="tp-brand-logo-auth" /></div>
|
||||
<h1 class="h3">{{ $title }}</h1>
|
||||
@if($description)<p class="text-body-secondary">{{ $description }}</p>@endif
|
||||
@if($errors->any())<div class="alert alert-danger"><ul class="mb-0">@foreach($errors->all() as $error)<li>{{ $error }}</li>@endforeach</ul></div>@endif
|
||||
{{ $slot }}
|
||||
</div></div>
|
||||
</div></div>
|
||||
</main>
|
||||
@@ -0,0 +1,4 @@
|
||||
<span class="tp-brand-logo {{ $class }}" style="--tp-logo-height: {{ $height }}">
|
||||
<img class="tp-brand-logo-light" src="{{ asset('images/branding/TrafficPumper-logo-koyu.png') }}" width="2172" height="724" alt="TrafficPumper" decoding="async" fetchpriority="high">
|
||||
<img class="tp-brand-logo-dark" src="{{ asset('images/branding/TrafficPumper-logo-acik.png') }}" width="2172" height="724" alt="TrafficPumper" decoding="async" fetchpriority="high">
|
||||
</span>
|
||||
@@ -0,0 +1,7 @@
|
||||
@props(['href' => null, 'height' => '44px', 'class' => ''])
|
||||
@php($content = view('components.brand-logo-images', compact('height', 'class')))
|
||||
@if($href)
|
||||
<a href="{{ $href }}" class="tp-brand-logo-link" aria-label="TrafficPumper">{!! $content !!}</a>
|
||||
@else
|
||||
{!! $content !!}
|
||||
@endif
|
||||
@@ -0,0 +1 @@
|
||||
<div class="tp-customer-shell"><x-customer-sidebar /><main {{ $attributes->class(['tp-customer-main']) }}>{{ $slot }}</main></div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<button class="btn btn-outline-secondary d-lg-none tp-sidebar-toggle" type="button" data-bs-toggle="offcanvas" data-bs-target="#customerSidebar" aria-label="{{__('customer.open_menu')}}">☰</button><aside class="offcanvas-lg offcanvas-start tp-customer-sidebar" tabindex="-1" id="customerSidebar"><div class="offcanvas-header tp-sidebar-closebar"><button type="button" class="btn-close btn-close-white d-lg-none" data-bs-dismiss="offcanvas"></button></div><div class="offcanvas-body"><nav class="tp-side-nav">
|
||||
<a class="tp-side-link {{request()->routeIs('dashboard')?'active':''}}" href="{{route('dashboard')}}"><span>⌂</span>{{__('customer.dashboard')}}</a>
|
||||
<div class="tp-nav-section"><button data-bs-toggle="collapse" data-bs-target="#rankTracking">{{__('customer.rank_tracking')}} <span>⌄</span></button><div class="collapse show" id="rankTracking"><a href="{{route('dashboard')}}">{{__('customer.overview')}}</a><a class="{{request()->routeIs('projects.*')?'active':''}}" href="{{route('projects.index')}}">{{__('customer.projects')}}</a><a class="{{request()->routeIs('keywords.*')?'active':''}}" href="{{route('keywords.index')}}">{{__('customer.keywords')}}</a><a class="{{request()->routeIs('rankings.*')?'active':''}}" href="{{route('rankings.index')}}">{{__('customer.ranking_history')}}</a><a class="{{request()->routeIs('serp-checks.*')?'active':''}}" href="{{route('serp-checks.index')}}">{{__('customer.serp_checks')}}</a></div></div>
|
||||
<div class="tp-nav-section"><button data-bs-toggle="collapse" data-bs-target="#reports">{{__('customer.reports')}} <span>⌄</span></button><div class="collapse {{request()->routeIs('reports.*','competitors.*')?'show':''}}" id="reports"><a class="{{request()->routeIs('reports.visibility')?'active':''}}" href="{{route('reports.visibility')}}">{{__('customer.visibility')}}</a><a class="{{request()->routeIs('reports.winners')?'active':''}}" href="{{route('reports.winners')}}">{{__('customer.winners')}}</a><a class="{{request()->routeIs('reports.losers')?'active':''}}" href="{{route('reports.losers')}}">{{__('customer.losers')}}</a><a class="{{request()->routeIs('reports.competitors','competitors.*')?'active':''}}" href="{{route('reports.competitors')}}">{{__('customer.competitors')}}</a></div></div>
|
||||
<div class="tp-nav-section"><button data-bs-toggle="collapse" data-bs-target="#account">{{__('customer.my_account')}} <span>⌄</span></button><div class="collapse {{request()->routeIs('profile.*','settings.*')?'show':''}}" id="account"><a class="{{request()->routeIs('profile.*')?'active':''}}" href="{{route('profile.show')}}">{{__('customer.profile')}}</a><a class="{{request()->routeIs('settings.*')?'active':''}}" href="{{route('settings.show')}}">{{__('customer.settings')}}</a></div></div>
|
||||
</nav></div></aside>
|
||||
@@ -0,0 +1,2 @@
|
||||
@props(['title','description'])
|
||||
<div class="tp-empty-state"><div aria-hidden="true">⌁</div><h3>{{ $title }}</h3><p>{{ $description }}</p></div>
|
||||
@@ -0,0 +1,22 @@
|
||||
@props(['title' => 'TrafficPumper'])
|
||||
@php($navigation = app(\App\Support\PortalNavigationContext::class)->resolve(request()))
|
||||
<!doctype html>
|
||||
<html lang="{{ app()->getLocale() }}" data-bs-theme="light">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
@if($navigation['kind'] === 'web')<meta name="trafficpumper-account-theme" content="{{ $navigation['user']->setting?->theme ?? 'system' }}"><meta name="trafficpumper-reduced-motion" content="{{ $navigation['user']->setting?->reduced_motion ? 'true' : 'false' }}">@endif
|
||||
<title>{{ $title }}</title>
|
||||
<script>{!! file_get_contents(resource_path('js/theme-manager.js')) !!}</script>
|
||||
@livewireStyles
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body>
|
||||
<x-public-topbar :context="$navigation" />
|
||||
@if (session('status'))<div class="container pt-3"><div class="alert alert-success">{{ session('status') }}</div></div>@endif
|
||||
{{ $slot }}
|
||||
@livewireScripts
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
@php($activeLocale = app()->getLocale())
|
||||
<div class="dropdown tp-locale-picker">
|
||||
<button class="btn btn-sm btn-outline-secondary dropdown-toggle d-inline-flex align-items-center gap-1" type="button" data-bs-toggle="dropdown" aria-expanded="false" aria-label="{{ __('ui.language') }}">
|
||||
<span aria-hidden="true">🌐</span><span>{{ strtoupper($activeLocale) }}</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow-sm">
|
||||
@foreach(['tr' => 'Türkçe', 'en' => 'English'] as $code => $label)
|
||||
<li><form method="POST" action="{{ route('locale.update') }}">@csrf
|
||||
<input type="hidden" name="return_to" value="{{ request()->getRequestUri() }}">
|
||||
<button name="locale" value="{{ $code }}" type="submit" class="dropdown-item d-flex align-items-center gap-2" @if($activeLocale === $code) aria-current="true" @endif>
|
||||
<span class="text-center" style="width:1rem" aria-hidden="true">{{ $activeLocale === $code ? '✓' : '' }}</span><span>{{ $label }}</span>
|
||||
</button>
|
||||
</form></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
@props(['label','value'=>null,'icon'=>'◎','description'=>null])
|
||||
@php($statusDescription=$description??(is_string($value)&&mb_strlen($value)>12?$value:null))
|
||||
<article class="tp-metric-card"><div class="tp-metric-icon" aria-hidden="true">{{ $icon }}</div><div class="tp-metric-content"><p class="tp-metric-label">{{ $label }}</p><strong class="tp-metric-value">{{ $statusDescription?'—':($value??'—') }}</strong>@if($statusDescription)<small class="tp-metric-description">{{ $statusDescription }}</small>@endif</div></article>
|
||||
@@ -0,0 +1,16 @@
|
||||
@props(['context'])
|
||||
@php($authenticated = $context['kind'] !== 'guest')
|
||||
@php($menuId = $authenticated ? 'tp-auth-mobile-menu' : 'tp-public-mobile-menu')
|
||||
<nav class="navbar tp-topbar border-bottom bg-body-tertiary"><div class="container-fluid py-2 gap-2"><div class="tp-public-topbar d-flex align-items-center w-100 gap-2">
|
||||
<x-brand-logo :href="$authenticated ? $context['panel_url'] : route('home')" height="44px" />
|
||||
<div class="tp-public-desktop-actions d-none d-lg-flex align-items-center gap-2 ms-auto"><a class="btn btn-link text-decoration-none" href="{{route('earn')}}">{{__('earn.navigation')}}</a>
|
||||
@if($authenticated)<a class="btn btn-link text-decoration-none" href="{{$context['panel_url']}}">{{$context['panel_label']}}</a><form method="POST" action="{{$context['logout_route']}}">@csrf<button class="btn btn-outline-secondary" type="submit">{{__('ui.sign_out')}}</button></form>
|
||||
@else<a class="btn btn-link text-decoration-none" href="{{route('earn.login')}}">{{__('earn_portal.login_title')}}</a><a class="btn btn-link text-decoration-none" href="{{route('login')}}">{{__('ui.sign_in')}}</a><a class="btn btn-primary" href="{{route('register')}}">{{__('ui.register')}}</a>@endif
|
||||
<x-locale-picker /><x-theme-picker /></div>
|
||||
<button class="navbar-toggler tp-public-menu-toggle d-lg-none ms-auto" type="button" data-bs-toggle="offcanvas" data-bs-target="#{{$menuId}}" aria-controls="{{$menuId}}" aria-expanded="false" aria-label="{{__('navigation.open_menu')}}"><span class="navbar-toggler-icon" aria-hidden="true"></span></button>
|
||||
</div></div></nav>
|
||||
<div class="offcanvas offcanvas-end tp-public-mobile-menu" tabindex="-1" id="{{$menuId}}" aria-labelledby="{{$menuId}}-label"><div class="offcanvas-header"><h2 class="offcanvas-title h5" id="{{$menuId}}-label">{{__('navigation.menu')}}</h2><button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="{{__('navigation.close_menu')}}"></button></div><div class="offcanvas-body d-flex flex-column gap-3">
|
||||
<a class="btn btn-link" href="{{route('earn')}}">{{__('earn.navigation')}}</a>
|
||||
@if($authenticated)<a class="btn btn-primary" href="{{$context['panel_url']}}">{{$context['panel_label']}}</a><form method="POST" action="{{$context['logout_route']}}">@csrf<button class="btn btn-outline-secondary w-100" type="submit">{{__('ui.sign_out')}}</button></form>
|
||||
@else<a class="btn btn-outline-primary" href="{{route('earn.login')}}">{{__('earn_portal.login_title')}}</a><a class="btn btn-outline-primary" href="{{route('login')}}">{{__('ui.sign_in')}}</a><a class="btn btn-primary" href="{{route('register')}}">{{__('ui.register')}}</a>@endif
|
||||
<div class="tp-public-mobile-preferences d-flex align-items-center gap-2 pt-2 border-top" aria-label="{{__('ui.preferences')}}"><x-locale-picker /><x-theme-picker /></div></div></div>
|
||||
@@ -0,0 +1,2 @@
|
||||
@props(['change'=>null])
|
||||
@if(is_null($change))<span class="badge text-bg-info">{{ __('customer.new') }}</span>@elseif($change>0)<span class="tp-rank-up" aria-label="{{ __('customer.improved_by',['count'=>$change]) }}">↑ {{ $change }}</span>@elseif($change<0)<span class="tp-rank-down" aria-label="{{ __('customer.declined_by',['count'=>abs($change)]) }}">↓ {{ abs($change) }}</span>@else<span class="text-body-secondary">— {{ __('customer.unchanged') }}</span>@endif
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="dropdown tp-theme-picker">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle d-flex align-items-center gap-2" type="button" data-bs-toggle="dropdown" aria-expanded="false" aria-label="{{ __('ui.theme') }}">
|
||||
<span data-theme-current-icon aria-hidden="true">◐</span><span data-theme-current-label>{{ __('ui.system') }}</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow-sm">
|
||||
@foreach(['light' => ['☀', __('ui.light')], 'dark' => ['☾', __('ui.dark')], 'system' => ['◐', __('ui.system')]] as $value => [$icon, $label])
|
||||
<li><button class="dropdown-item d-flex align-items-center gap-2" type="button" data-theme-value="{{ $value }}" data-theme-label="{{ $label }}" data-theme-icon="{{ $icon }}"><span aria-hidden="true">{{ $icon }}</span><span>{{ $label }}</span><span class="ms-auto" data-theme-check hidden aria-hidden="true">✓</span></button></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<x-layouts.app :title="__('customer.dashboard').' — TrafficPumper'"><div class="tp-customer-shell"><x-customer-sidebar/><main class="tp-customer-main">
|
||||
<span class="visually-hidden">{{ __('ui.welcome',['name'=>auth()->user()->name]) }}</span>
|
||||
<header class="tp-page-header"><div><p>{{ __('customer.rank_tracking') }}</p><h1>{{ __('customer.dashboard') }}</h1><span>{{ __('customer.dashboard_intro') }}</span></div><form class="tp-dashboard-filters" method="GET"><label>{{ __('customer.project') }}<select class="form-select" name="project" onchange="this.form.submit()"><option value="">{{ __('customer.all_projects') }}</option>@foreach($projects as $project)<option value="{{ $project->ulid }}" @selected($selectedProject?->id===$project->id)>{{ $project->name }}</option>@endforeach</select></label><label>{{ __('customer.period') }}<select class="form-select" name="days" onchange="this.form.submit()">@foreach([7,30,90] as $d)<option value="{{$d}}" @selected($days===$d)>{{ __('customer.last_days',['count'=>$d]) }}</option>@endforeach</select></label></form></header>
|
||||
<section class="tp-metric-grid"><x-metric-card :label="__('customer.active_projects')" :value="$metrics['projects']" icon="▣"/><x-metric-card :label="__('customer.tracked_keywords')" :value="$metrics['keywords']" icon="⌕"/><x-metric-card :label="__('customer.top10')" :value="$metrics['top10']" icon="↗"/><x-metric-card :label="__('customer.average_rank')" :value="$metrics['average']" icon="◎"/><x-metric-card :label="__('customer.rising')" :value="$metrics['winners']" icon="↑"/><x-metric-card :label="__('customer.falling')" :value="$metrics['losers']" icon="↓"/></section>
|
||||
<section class="tp-panel tp-chart-panel"><div class="tp-panel-heading"><div><p>{{ __('customer.performance') }}</p><h2>{{ __('customer.ranking_performance') }}</h2></div></div>@if($rows->whereNotNull('current')->isEmpty())<x-empty-state :title="__('customer.no_rank_data')" :description="__('customer.after_first_check')"/>@else<div class="tp-rank-bars">@foreach($rows->whereNotNull('current')->take(10) as $row)<div title="{{ $row['keyword']->phrase }}: {{ $row['current'] }}"><span>{{ Str::limit($row['keyword']->phrase,20) }}</span><i style="--rank:{{ min($row['current'],100) }}"></i><strong>#{{ $row['current'] }}</strong></div>@endforeach</div>@endif</section>
|
||||
<section class="tp-panel"><div class="tp-panel-heading"><div><p>{{ __('customer.latest_data') }}</p><h2>{{ __('customer.keyword_summary') }}</h2></div></div><div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{ __('customer.keyword') }}</th><th>{{ __('customer.project') }}</th><th>{{ __('customer.current_rank') }}</th><th>{{ __('customer.previous_rank') }}</th><th>{{ __('customer.change') }}</th><th>{{ __('customer.target') }}</th><th>{{ __('customer.last_check') }}</th></tr></thead><tbody>@forelse($rows as $row)<tr><td><strong>{{ $row['keyword']->phrase }}</strong></td><td>{{ $row['project']->name }}</td><td>{{ $row['current']?'#'.$row['current']:'—' }}</td><td>{{ $row['previous']?'#'.$row['previous']:'—' }}</td><td><x-rank-change :change="$row['change']"/></td><td>{{ $row['keyword']->search_engine->label() }} · {{ $row['keyword']->device->label() }}</td><td>{{ $row['checked_at']?->diffForHumans()??'—' }}</td></tr>@empty<tr><td colspan="7"><x-empty-state :title="__('customer.no_keywords')" :description="__('customer.add_keyword_hint')"/></td></tr>@endforelse</tbody></table></div></section>
|
||||
<div class="tp-two-columns"><section class="tp-panel"><div class="tp-panel-heading"><h2>{{ __('customer.top_winners') }}</h2></div>@forelse($winners as $row)<div class="tp-mover"><span><strong>{{ $row['keyword']->phrase }}</strong><small>{{ $row['project']->name }}</small></span><x-rank-change :change="$row['change']"/></div>@empty<x-empty-state :title="__('customer.no_movement')" :description="__('customer.after_two_checks')"/>@endforelse</section><section class="tp-panel"><div class="tp-panel-heading"><h2>{{ __('customer.top_losers') }}</h2></div>@forelse($losers as $row)<div class="tp-mover"><span><strong>{{ $row['keyword']->phrase }}</strong><small>{{ $row['project']->name }}</small></span><x-rank-change :change="$row['change']"/></div>@empty<x-empty-state :title="__('customer.no_movement')" :description="__('customer.after_two_checks')"/>@endforelse</section></div>
|
||||
</main></div></x-layouts.app>
|
||||
@@ -0,0 +1,12 @@
|
||||
<x-layouts.app :title="__('earn_portal.login_title').' — TrafficPumper'">
|
||||
<x-auth-card :title="__('earn_portal.login_title')" :description="__('earn_portal.login_description')">
|
||||
<form method="POST" action="{{ route('earn.login.store') }}">@csrf
|
||||
<div class="mb-3"><label class="form-label" for="email">{{ __('ui.email') }}</label><input class="form-control" id="email" name="email" type="email" value="{{ old('email') }}" required autofocus autocomplete="email"></div>
|
||||
<div class="mb-3"><label class="form-label" for="password">{{ __('ui.password') }}</label><input class="form-control" id="password" name="password" type="password" required autocomplete="current-password"></div>
|
||||
<div class="form-check mb-3"><input class="form-check-input" id="remember" name="remember" type="checkbox"><label class="form-check-label" for="remember">{{ __('ui.remember') }}</label></div>
|
||||
<button class="btn btn-primary w-100" type="submit">{{ __('earn_portal.sign_in') }}</button>
|
||||
</form>
|
||||
<div class="d-flex flex-wrap justify-content-between gap-2 mt-3 small"><a href="{{ route('earn.register') }}">{{ __('earn_portal.create_account') }}</a><a href="{{ route('home') }}">{{ __('earn_portal.back_home') }}</a></div>
|
||||
<div class="d-flex align-items-center justify-content-center gap-2 mt-4 pt-3 border-top"><x-locale-picker /><x-theme-picker /></div>
|
||||
</x-auth-card>
|
||||
</x-layouts.app>
|
||||
@@ -0,0 +1,15 @@
|
||||
<x-layouts.app :title="__('earn_portal.register_title').' — TrafficPumper'">
|
||||
<x-auth-card :title="__('earn_portal.register_title')" :description="__('earn_portal.register_description')">
|
||||
<form method="POST" action="{{ route('earn.register.store') }}">@csrf
|
||||
<div class="mb-3"><label class="form-label" for="name">{{ __('earn_portal.name') }}</label><input class="form-control" id="name" name="name" value="{{ old('name') }}" required autocomplete="name"></div>
|
||||
<div class="mb-3"><label class="form-label" for="email">{{ __('ui.email') }}</label><input class="form-control" id="email" name="email" type="email" value="{{ old('email') }}" required autocomplete="email"></div>
|
||||
<div class="mb-3"><label class="form-label" for="password">{{ __('ui.password') }}</label><input class="form-control" id="password" name="password" type="password" required autocomplete="new-password"></div>
|
||||
<div class="mb-3"><label class="form-label" for="password_confirmation">{{ __('earn_portal.password_confirmation') }}</label><input class="form-control" id="password_confirmation" name="password_confirmation" type="password" required autocomplete="new-password"></div>
|
||||
<div class="form-check mb-2"><input class="form-check-input @error('terms_accepted') is-invalid @enderror" id="terms_accepted" name="terms_accepted" type="checkbox" value="1" @checked(old('terms_accepted')) required><label class="form-check-label" for="terms_accepted">{!! __('earn_legal.form.terms', ['link' => '<a href="'.route('earn.terms').'" target="_blank" rel="noopener noreferrer">'.__('earn_legal.form.terms_link').'</a>']) !!}</label>@error('terms_accepted')<div class="invalid-feedback">{{ $message }}</div>@enderror</div>
|
||||
<div class="form-check mb-3"><input class="form-check-input @error('privacy_notice_acknowledged') is-invalid @enderror" id="privacy_notice_acknowledged" name="privacy_notice_acknowledged" type="checkbox" value="1" @checked(old('privacy_notice_acknowledged')) required><label class="form-check-label" for="privacy_notice_acknowledged">{!! __('earn_legal.form.privacy', ['link' => '<a href="'.route('earn.privacy').'" target="_blank" rel="noopener noreferrer">'.__('earn_legal.form.privacy_link').'</a>']) !!}</label>@error('privacy_notice_acknowledged')<div class="invalid-feedback">{{ $message }}</div>@enderror</div>
|
||||
<button class="btn btn-primary w-100" type="submit">{{ __('earn_portal.register') }}</button>
|
||||
</form>
|
||||
<div class="d-flex flex-wrap justify-content-between gap-2 mt-3 small"><a href="{{ route('earn.login') }}">{{ __('earn_portal.have_account') }}</a><a href="{{ route('home') }}">{{ __('earn_portal.back_home') }}</a></div>
|
||||
<div class="d-flex align-items-center justify-content-center gap-2 mt-4 pt-3 border-top"><x-locale-picker /><x-theme-picker /></div>
|
||||
</x-auth-card>
|
||||
</x-layouts.app>
|
||||
@@ -0,0 +1,6 @@
|
||||
<x-layouts.app :title="__('earn_portal.verify_title').' — TrafficPumper'">
|
||||
<x-auth-card :title="__('earn_portal.verify_title')" :description="__('earn_portal.verify_description')">
|
||||
<form method="POST" action="{{ route('earn.verification.send') }}">@csrf<button class="btn btn-primary w-100">{{ __('earn_portal.resend_verification') }}</button></form>
|
||||
<form method="POST" action="{{ route('earn.logout') }}" class="mt-3">@csrf<button class="btn btn-outline-secondary w-100">{{ __('ui.sign_out') }}</button></form>
|
||||
</x-auth-card>
|
||||
</x-layouts.app>
|
||||
@@ -0,0 +1,11 @@
|
||||
<x-layouts.app :title="$content['title'].' — TrafficPumper Earn'">
|
||||
<main class="container py-4 py-lg-5 tp-legal-document">
|
||||
<div class="row justify-content-center"><article class="col-12 col-lg-10 col-xl-9">
|
||||
<a href="{{ route('earn.register') }}" class="d-inline-block mb-3">← {{ __('earn_legal.back') }}</a>
|
||||
<header class="mb-4"><p class="text-primary fw-semibold mb-2">TRAFFICPUMPER EARN</p><h1 class="display-6">{{ $content['title'] }}</h1><div class="alert alert-warning"><strong>{{ __('earn_legal.draft') }}</strong><br>{{ __('earn_legal.version') }}: {{ $version }} · {{ __('earn_legal.effective_date') }}: {{ $effectiveDate }}</div><p class="lead">{{ $content['intro'] }}</p></header>
|
||||
<nav class="card mb-4" aria-label="{{ __('earn_legal.contents') }}"><div class="card-body"><h2 class="h5">{{ __('earn_legal.contents') }}</h2><ol class="row g-2 mb-0">@foreach($content['sections'] as $id => $section)<li class="col-12 col-md-6"><a href="#{{ $id }}">{{ $section['title'] }}</a></li>@endforeach</ol></div></nav>
|
||||
@foreach($content['sections'] as $id => $section)<section id="{{ $id }}" class="tp-legal-section py-3 border-bottom" tabindex="-1"><h2 class="h4"><a class="text-reset text-decoration-none" href="#{{ $id }}">{{ $section['title'] }}</a></h2>@foreach($section['paragraphs'] as $paragraph)<p>{{ $paragraph }}</p>@endforeach</section>@endforeach
|
||||
<p class="mt-4 text-body-secondary">{{ __('earn_legal.review_notice') }}</p>
|
||||
</article></div>
|
||||
</main>
|
||||
</x-layouts.app>
|
||||
@@ -0,0 +1,19 @@
|
||||
<x-layouts.app :title="__('earn_portal.api_keys').' — TrafficPumper'">
|
||||
<main class="container py-4 py-md-5 tp-api-keys">
|
||||
<header class="d-flex flex-wrap align-items-start justify-content-between gap-3 mb-4"><div><a href="{{route('earn.dashboard')}}" class="small">← {{__('earn_portal.dashboard')}}</a><h1 class="h2 mt-2 mb-1">{{__('earn_portal.api_keys')}}</h1><p class="text-body-secondary mb-0">{{__('earn_portal.api_keys_description')}}</p><p class="small text-body-secondary mt-2 mb-0">{{__('earn_portal.key_usage',['used'=>$tokens->count(),'max'=>$maxTotal])}}</p></div><button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createApiKey" @disabled($tokens->count() >= $maxTotal)>{{__('earn_portal.new_key')}}</button></header>
|
||||
@error('api_key')<div class="alert alert-danger" role="alert">{{$message}}</div>@enderror
|
||||
@if($tokens->count() >= $maxTotal)<div class="alert alert-warning">{{__('earn_portal.total_limit_reached')}}</div>@endif
|
||||
@if(session('new_api_key')) @php($newRecord=$tokens->first())<section class="alert alert-success tp-api-key-created"><h2 class="h5">{{__('earn_portal.created_title')}}</h2><p>{{__('earn_portal.created_warning')}}</p><div class="input-group mb-3"><input id="newApiKeyValue" class="form-control font-monospace" type="password" readonly value="{{session('new_api_key')}}" aria-label="{{__('earn_portal.api_key')}}"><button class="btn btn-outline-secondary" type="button" data-token-toggle data-show="{{__('earn_portal.show')}}" data-hide="{{__('earn_portal.hide')}}">{{__('earn_portal.show')}}</button><button class="btn btn-outline-secondary" type="button" data-token-copy data-copied="{{__('earn_portal.copied')}}">{{__('earn_portal.copy')}}</button></div><dl class="tp-key-meta"><div><dt>{{__('earn_portal.device_name')}}</dt><dd>{{$newRecord?->name}}</dd></div><div><dt>{{__('earn_portal.ability')}}</dt><dd><code>tasks:read</code></dd></div><div><dt>{{__('earn_portal.expires_at')}}</dt><dd>{{$newRecord?->expires_at?->translatedFormat('j F Y H:i') ?? __('earn_portal.no_expiration')}}</dd></div></dl><a href="{{route('earn.api-keys.index')}}" class="btn btn-success">{{__('earn_portal.done')}}</a></section>@endif
|
||||
@if($tokens->isEmpty())<section class="card text-center tp-api-empty"><div class="card-body"><div class="tp-key-icon" aria-hidden="true">⌁</div><h2 class="h4">{{__('earn_portal.empty_title')}}</h2><p class="text-body-secondary">{{__('earn_portal.empty_description')}}</p><button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createApiKey">{{__('earn_portal.first_key')}}</button></div></section>
|
||||
@else<section class="card"><div class="card-body"><p class="small text-body-secondary">{{__('earn_portal.lifecycle_help')}}</p><div class="table-responsive"><table class="table align-middle mb-0 tp-api-table"><thead><tr><th>{{__('earn_portal.device_name')}}</th><th>{{__('earn_portal.key_preview')}}</th><th>{{__('earn_portal.device_binding')}}</th><th>{{__('earn_portal.device_id_preview')}}</th><th>{{__('earn_portal.first_connection')}}</th><th>{{__('earn_portal.last_seen')}}</th><th>{{__('earn_portal.first_ip')}}</th><th>{{__('earn_portal.last_ip')}}</th><th>{{__('earn_portal.expires_at')}}</th><th>{{__('earn_portal.status')}}</th><th>{{__('earn_portal.actions')}}</th></tr></thead><tbody>
|
||||
@foreach($tokens as $token) @php($state=$token->status()->value)<tr><td data-label="{{__('earn_portal.device_name')}}">{{$token->name}}</td><td data-label="{{__('earn_portal.key_preview')}}"><code>tpe_••••••••{{str($token->token_hint)->after('tpe_')}}</code></td><td data-label="{{__('earn_portal.device_binding')}}">{{__($token->device_id_hash ? ($token->device_mismatch_seen ? 'earn_portal.device_mismatch_seen' : 'earn_portal.device_bound') : 'earn_portal.device_unbound')}}</td><td data-label="{{__('earn_portal.device_id_preview')}}"><code>{{$token->device_id_preview ?? '—'}}</code></td><td data-label="{{__('earn_portal.first_connection')}}">{{$token->device_bound_at?->translatedFormat('j F Y H:i') ?? '—'}}</td><td data-label="{{__('earn_portal.last_seen')}}">{{$token->last_seen_at?->translatedFormat('j F Y H:i') ?? __('earn_portal.never_used')}}</td><td data-label="{{__('earn_portal.first_ip')}}">{{$token->first_ip ?? '—'}}</td><td data-label="{{__('earn_portal.last_ip')}}">{{$token->last_ip ?? '—'}}</td><td data-label="{{__('earn_portal.expires_at')}}">{{$token->expires_at?->translatedFormat('j F Y H:i') ?? __('earn_portal.no_expiration')}}</td><td data-label="{{__('earn_portal.status')}}">
|
||||
@if(in_array($state,['active','inactive']))<form method="POST" action="{{route('earn.api-keys.status',$token->id)}}" class="tp-status-switch">@csrf @method('PATCH')<input type="hidden" name="is_active" value="{{$state==='active'?'0':'1'}}"><span>{{__('earn_portal.status_'.$state)}}</span><button type="submit" class="form-check-input" role="switch" aria-checked="{{$state==='active'?'true':'false'}}" aria-label="{{__($state==='active'?'earn_portal.deactivate':'earn_portal.activate')}}" data-status-switch></button></form>@else<span class="badge text-bg-{{$state==='expired'?'warning':'secondary'}}">{{__('earn_portal.status_'.$state)}}</span>@endif</td>
|
||||
<td data-label="{{__('earn_portal.actions')}}"><div class="dropdown"><button class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false" aria-label="{{__('earn_portal.actions')}}">⋮</button><ul class="dropdown-menu dropdown-menu-end">@if(in_array($state,['active','inactive']))<li><form method="POST" action="{{route('earn.api-keys.status',$token->id)}}">@csrf @method('PATCH')<input type="hidden" name="is_active" value="{{$state==='active'?'0':'1'}}"><button class="dropdown-item">{{__($state==='active'?'earn_portal.deactivate':'earn_portal.activate')}}</button></form></li><li><button class="dropdown-item text-warning" data-action-key data-kind="revoke" data-url="{{route('earn.api-keys.revoke',$token->id)}}" data-name="{{$token->name}}" data-bs-toggle="modal" data-bs-target="#keyActionModal">{{__('earn_portal.permanent_revoke')}}</button></li>@endif<li><button class="dropdown-item text-danger" data-action-key data-kind="delete" data-url="{{route('earn.api-keys.destroy',$token->id)}}" data-name="{{$token->name}}" data-bs-toggle="modal" data-bs-target="#keyActionModal">{{__('earn_portal.delete')}}</button></li></ul></div></td></tr>@endforeach
|
||||
</tbody></table></div></div></section>@endif
|
||||
<section class="card mt-4"><div class="card-body"><h2 class="h5">{{__('earn_portal.example_title')}}</h2><pre class="tp-code-example"><code>curl -X GET "{{$tasksUrl}}" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer {{__('earn_portal.placeholder_key')}}" \
|
||||
-H "X-Device-ID: 5c9f63d12166490bb6a6459af51e8b83"</code></pre></div></section></main>
|
||||
<div class="modal fade" id="createApiKey" tabindex="-1" aria-labelledby="createApiKeyTitle" aria-hidden="true"><div class="modal-dialog modal-dialog-centered"><div class="modal-content"><form method="POST" action="{{route('earn.api-keys.store')}}">@csrf<div class="modal-header"><h2 class="modal-title fs-5" id="createApiKeyTitle">{{__('earn_portal.new_key')}}</h2><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{__('earn_portal.cancel')}}"></button></div><div class="modal-body"><label class="form-label" for="keyName">{{__('earn_portal.device_name')}}</label><input id="keyName" class="form-control" name="name" value="{{old('name')}}" minlength="3" maxlength="80" required autocomplete="off"><div class="form-text">{{__('earn_portal.device_help')}}</div><label class="form-label mt-3" for="duration">{{__('earn_portal.expiration')}}</label><select id="duration" class="form-select" name="duration" required>@foreach($durations as $duration)<option value="{{$duration}}" @selected(old('duration',$defaultDuration)===$duration)>{{__('earn_portal.duration_'.$duration)}}</option>@endforeach</select><p class="form-text">{{__('earn_portal.unlimited_warning')}}</p><p class="form-text mb-0">{{__('earn_portal.fixed_ability')}} <code>tasks:read</code></p></div><div class="modal-footer"><button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{{__('earn_portal.cancel')}}</button><button class="btn btn-primary">{{__('earn_portal.create_key')}}</button></div></form></div></div></div>
|
||||
<div class="modal fade" id="keyActionModal" tabindex="-1" aria-labelledby="keyActionTitle" aria-hidden="true"><div class="modal-dialog modal-dialog-centered"><div class="modal-content"><form method="POST" data-action-form>@csrf<div data-action-method></div><div class="modal-header"><h2 class="modal-title fs-5" id="keyActionTitle" data-action-title></h2><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{__('earn_portal.cancel')}}"></button></div><div class="modal-body"><p data-action-message data-revoke-template="{{__('earn_portal.revoke_confirmation',['name'=>':name'])}}" data-delete-template="{{__('earn_portal.delete_confirmation',['name'=>':name'])}}"></p></div><div class="modal-footer"><button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{{__('earn_portal.cancel')}}</button><button class="btn btn-danger" data-action-submit></button></div></form></div></div></div>
|
||||
</x-layouts.app>
|
||||
@@ -0,0 +1,6 @@
|
||||
<x-layouts.app :title="__('earn_portal.dashboard').' — TrafficPumper'">
|
||||
<main class="container py-4 py-md-5">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4"><div><p class="text-primary fw-semibold mb-1">TRAFFICPUMPER EARN</p><h1 class="h2 mb-0">{{ __('earn_portal.dashboard') }}</h1></div><div class="d-flex gap-2"><a class="btn btn-primary" href="{{ route('earn.api-keys.index') }}">{{ __('earn_portal.api_keys') }}</a><form method="POST" action="{{ route('earn.logout') }}">@csrf<button class="btn btn-outline-secondary">{{ __('ui.sign_out') }}</button></form></div></div>
|
||||
<div class="row g-3"><div class="col-12 col-md-4"><section class="card h-100"><div class="card-body"><h2 class="h6">{{ __('earn_portal.account_status') }}</h2><p class="h4 text-success mb-0">{{ __('earn_portal.status_active') }}</p></div></section></div><div class="col-12 col-md-4"><section class="card h-100"><div class="card-body"><h2 class="h6">{{ __('earn_portal.api_access') }}</h2><p class="h4 mb-0">{{ __('earn_portal.access_ready') }}</p></div></section></div><div class="col-12 col-md-4"><section class="card h-100"><div class="card-body"><h2 class="h6">{{ __('earn_portal.active_keys') }}</h2><p class="display-6 mb-0">{{ $activeTokenCount }}</p></div></section></div></div>
|
||||
<div class="alert alert-info mt-4">{{ __('earn_portal.test_stage') }}</div>
|
||||
</main></x-layouts.app>
|
||||
@@ -0,0 +1,29 @@
|
||||
<x-layouts.app :title="__('earn.meta_title')">
|
||||
<main class="tp-earn">
|
||||
<section class="tp-earn-hero">
|
||||
<div class="container">
|
||||
<div class="tp-earn-hero-copy">
|
||||
<span class="tp-landing-eyebrow">{{ __('earn.eyebrow') }}</span>
|
||||
<h1>{{ __('earn.hero_title') }}</h1>
|
||||
<p>{{ __('earn.hero_description') }}</p>
|
||||
<div class="tp-landing-actions"><a class="btn btn-primary btn-lg" href="#availability">{{ __('earn.early_access') }}</a><a class="btn btn-outline-secondary btn-lg" href="#how-it-works">{{ __('earn.how_link') }}</a></div>
|
||||
<span class="tp-coming-soon">{{ __('earn.coming_soon') }}</span>
|
||||
</div>
|
||||
<div class="tp-device-scene" aria-label="{{ __('earn.device_illustration') }}">
|
||||
<div class="tp-device tp-device-desktop"><span></span><div><i></i><i></i><i></i><strong>{{ __('earn.measurement_session') }}</strong></div></div>
|
||||
<div class="tp-device tp-device-mobile"><span></span><div><i></i><i></i><strong>{{ __('earn.verified_task') }}</strong></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tp-earn-rate" aria-labelledby="rate-title"><div class="container"><div><span>{{ __('earn.rate_label') }}</span><h2 id="rate-title">{{ number_format($tasksPerUsd, 0, ',', '.') }} {{ __('earn.tasks') }} = 1 USD</h2><p>{{ __('earn.rate_explanation') }}</p></div><dl><div><dt>{{ __('earn.minimum_payout') }}</dt><dd>{{ $minimumPayoutUsd }} USD</dd></div><div><dt>{{ __('earn.required_total') }}</dt><dd>{{ number_format($minimumPayoutTasks, 0, ',', '.') }} {{ __('earn.tasks') }}</dd></div></dl></div></section>
|
||||
|
||||
<section class="tp-landing-section" id="how-it-works" aria-labelledby="process-title"><div class="container"><div class="tp-section-heading"><span>{{ __('earn.process_eyebrow') }}</span><h2 id="process-title">{{ __('earn.process_title') }}</h2></div><ol class="tp-earn-process">@foreach(range(1,6) as $step)<li><span>{{ $step }}</span><div><h3>{{ __('earn.steps.'.$step.'.title', ['tasks' => number_format($tasksPerUsd, 0, ',', '.'), 'usd' => $minimumPayoutUsd]) }}</h3><p>{{ __('earn.steps.'.$step.'.description') }}</p></div></li>@endforeach</ol></div></section>
|
||||
|
||||
<section class="tp-landing-section tp-earn-calculation" aria-labelledby="calculation-title"><div class="container"><div class="tp-section-heading"><span>{{ __('earn.calculation_eyebrow') }}</span><h2 id="calculation-title">{{ __('earn.calculation_title') }}</h2><p>{{ __('earn.calculation_description') }}</p></div><div class="tp-calculation-grid">@foreach([1,10,$minimumPayoutUsd] as $usd)<article @class(['tp-calculation-card','tp-payout-card'=>$usd===$minimumPayoutUsd])><strong>{{ number_format($tasksPerUsd * $usd, 0, ',', '.') }}</strong><span>{{ __('earn.tasks') }}</span><i aria-hidden="true">→</i><b>{{ $usd }} USD</b>@if($usd===$minimumPayoutUsd)<small>{{ __('earn.payout_eligible') }}</small>@endif</article>@endforeach</div><ul class="tp-earn-disclosures"><li>{{ __('earn.only_verified') }}</li><li>{{ __('earn.availability_not_guaranteed') }}</li><li>{{ __('earn.no_fixed_income') }}</li></ul></div></section>
|
||||
|
||||
<section class="tp-landing-section tp-earn-safety" aria-labelledby="safety-title"><div class="container"><div class="tp-section-heading"><span>{{ __('earn.safety_eyebrow') }}</span><h2 id="safety-title">{{ __('earn.safety_title') }}</h2><p>{{ __('earn.safety_description') }}</p></div><ul class="tp-safety-grid">@foreach(range(1,11) as $item)<li><span aria-hidden="true">✓</span>{{ __('earn.safety.'.$item) }}</li>@endforeach</ul></div></section>
|
||||
|
||||
<section class="tp-landing-cta" id="availability"><div class="container"><div><h2>{{ __('earn.cta_title') }}</h2><p>{{ __('earn.cta_description') }}</p></div><span class="tp-coming-soon tp-coming-soon-light">{{ __('earn.coming_soon') }}</span></div></section>
|
||||
</main>
|
||||
</x-layouts.app>
|
||||
@@ -0,0 +1,92 @@
|
||||
<style>
|
||||
:root { --trafficpumper-admin-topbar-height: 4rem; }
|
||||
.fi-body-has-topbar .fi-topbar { min-height: var(--trafficpumper-admin-topbar-height); padding-inline: 1rem; }
|
||||
.fi-body-has-topbar .fi-topbar-start { width: calc(var(--sidebar-width) - 1rem); flex: 0 0 calc(var(--sidebar-width) - 1rem); margin-inline-end: 0; }
|
||||
.fi-body-has-topbar .fi-topbar-start .fi-logo { height: 2.5rem; width: auto; max-width: min(15rem, 100%); margin-inline-start: 0; object-fit: contain; object-position: left center; }
|
||||
.fi-body-has-topbar .fi-sidebar-header-ctn { display: none; }
|
||||
.fi-body-has-topbar .fi-sidebar-nav { padding-top: 1.5rem; }
|
||||
.fi-body-has-topbar .fi-topbar-end {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
min-width: 0;
|
||||
gap: .5rem;
|
||||
margin-block: 0;
|
||||
}
|
||||
.trafficpumper-admin-preferences {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
flex-wrap: nowrap;
|
||||
gap: .5rem;
|
||||
margin-block: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tp-admin-control {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: .375rem;
|
||||
height: 2.5rem;
|
||||
min-width: 2.5rem;
|
||||
padding-inline: .625rem;
|
||||
border: 1px solid rgb(229 231 235);
|
||||
border-radius: .625rem;
|
||||
color: rgb(55 65 81);
|
||||
background: transparent;
|
||||
font-size: .75rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
.tp-admin-control:hover { background: rgb(249 250 251); color: rgb(17 24 39); }
|
||||
.tp-admin-control:focus-visible { outline: 2px solid rgb(245 158 11); outline-offset: 2px; }
|
||||
.dark .tp-admin-control { border-color: rgb(55 65 81); color: rgb(209 213 219); }
|
||||
.dark .tp-admin-control:hover { background: rgb(31 41 55); color: rgb(255 255 255); }
|
||||
.tp-admin-control .fi-icon { width: 1.125rem; height: 1.125rem; }
|
||||
.tp-admin-control-chevron { width: .875rem !important; height: .875rem !important; }
|
||||
.tp-admin-theme-icon { width: 1.125rem; font-size: 1rem; text-align: center; }
|
||||
.tp-admin-user-control {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
gap: .25rem;
|
||||
height: 2.5rem;
|
||||
margin: 0;
|
||||
padding: .125rem .375rem .125rem .125rem;
|
||||
border-radius: .625rem;
|
||||
}
|
||||
.tp-admin-user-control:hover { background: rgb(249 250 251); }
|
||||
.dark .tp-admin-user-control:hover { background: rgb(31 41 55); }
|
||||
.tp-admin-user-control:focus-visible { outline: 2px solid rgb(245 158 11); outline-offset: 2px; }
|
||||
.tp-admin-avatar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 9999px;
|
||||
background: rgb(245 158 11);
|
||||
color: rgb(17 24 39);
|
||||
font-size: .75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.tp-admin-user-chevron { width: .875rem; height: .875rem; color: rgb(107 114 128); }
|
||||
.tp-admin-user-name, .tp-admin-user-email { display: block; max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.tp-admin-user-email { margin-top: .125rem; color: rgb(107 114 128); font-size: .75rem; font-weight: 400; }
|
||||
@media (max-width: 63.999rem) {
|
||||
.fi-body-has-topbar .fi-topbar { gap: .5rem; }
|
||||
.fi-body-has-topbar .fi-topbar-start { display: flex; width: auto; flex: 1 1 auto; margin: 0; min-width: 0; }
|
||||
.fi-body-has-topbar .fi-topbar-start .fi-logo { height: 2.25rem; max-width: min(12rem, 42vw); }
|
||||
.fi-body-has-topbar .fi-topbar-end { gap: .5rem; }
|
||||
}
|
||||
@media (max-width: 39.999rem) {
|
||||
.fi-body-has-topbar .fi-topbar { padding-inline: .75rem; }
|
||||
.fi-body-has-topbar .fi-topbar-start .fi-logo { height: 2rem; max-width: 32vw; }
|
||||
.trafficpumper-admin-preferences, .fi-body-has-topbar .fi-topbar-end { gap: .25rem; }
|
||||
.tp-admin-control { padding-inline: .5rem; }
|
||||
.tp-admin-control-label, .tp-admin-control-chevron { display: none; }
|
||||
.tp-admin-user-control { width: 2.5rem; padding: .125rem; }
|
||||
.tp-admin-user-chevron { display: none; }
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
@php($activeLocale = app()->getLocale())
|
||||
<x-filament::dropdown placement="bottom-end">
|
||||
<x-slot name="trigger">
|
||||
<button type="button" class="tp-admin-control" aria-label="{{ __('ui.language') }}" title="{{ __('ui.language') }}">
|
||||
<x-filament::icon icon="heroicon-o-language" class="fi-icon fi-size-lg" />
|
||||
<span class="tp-admin-control-label">{{ strtoupper($activeLocale) }}</span>
|
||||
<x-filament::icon icon="heroicon-m-chevron-down" class="tp-admin-control-chevron" />
|
||||
</button>
|
||||
</x-slot>
|
||||
<x-filament::dropdown.list>
|
||||
@foreach(['tr' => 'Türkçe', 'en' => 'English'] as $code => $label)
|
||||
<form method="POST" action="{{ route('locale.update') }}">@csrf
|
||||
<input type="hidden" name="return_to" value="{{ request()->getRequestUri() }}">
|
||||
<x-filament::dropdown.list.item type="submit" name="locale" :value="$code" :icon="$activeLocale === $code ? 'heroicon-o-check' : null">
|
||||
{{ $label }}
|
||||
</x-filament::dropdown.list.item>
|
||||
</form>
|
||||
@endforeach
|
||||
</x-filament::dropdown.list>
|
||||
</x-filament::dropdown>
|
||||
@@ -0,0 +1,11 @@
|
||||
<x-filament-panels::page>
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<x-filament::section><x-slot name="heading">Aktivasyon</x-slot><p>{{ $summary['activation_mode'] }}</p><p>Kill switch: {{ $summary['kill_switch'] ? 'AKTİF' : 'Kapalı' }}</p><p>Circuit: {{ $summary['circuit_open'] ? 'Açık' : 'Kapalı' }}</p></x-filament::section>
|
||||
<x-filament::section><x-slot name="heading">Son 24 saat</x-slot><p>Toplam: {{ $summary['checks_24h'] }}</p><p>Tamamlanan: {{ $summary['completed_24h'] }}</p><p>Başarısız: {{ $summary['failed_24h'] }}</p><p>Başarı: %{{ $summary['success_percent'] }}</p></x-filament::section>
|
||||
<x-filament::section><x-slot name="heading">Kuyruk</x-slot><p>Provider bekleyen: {{ $summary['waiting_provider'] }}</p><p>Takılı: {{ $summary['stuck'] }}</p><p>Backlog: {{ $summary['queue_backlog'] }}</p><p>Failed jobs: {{ $summary['failed_jobs'] }}</p></x-filament::section>
|
||||
<x-filament::section><x-slot name="heading">Bütçe (mikro USD)</x-slot><p>Günlük: {{ $summary['budget']['daily_micros'] }}</p><p>Aylık: {{ $summary['budget']['monthly_micros'] }}</p></x-filament::section>
|
||||
<x-filament::section><x-slot name="heading">Ramp-up</x-slot><p>Başlangıç: {{ $summary['ramp_up']['started_at'] ?? 'Başlamadı' }}</p><p>Kullanım: {{ $summary['ramp_up']['used'] }}</p><p>Geçerli limit: {{ $summary['ramp_up']['limit'] ?? 'Sınırsız' }}</p></x-filament::section>
|
||||
<x-filament::section><x-slot name="heading">Provider health</x-slot><p>Son kontrol: {{ $summary['last_health_at'] ?? 'Yapılmadı' }}</p><p>Credential: {{ config('serp.dataforseo.login') && config('serp.dataforseo.password') ? 'configured' : 'not configured' }}</p><p>Son hata: {{ $summary['last_error_category'] ?? '—' }}</p></x-filament::section>
|
||||
</div>
|
||||
@if(auth()->user()->hasRole('super_admin'))<div class="flex gap-3"><x-filament::button color="danger" wire:click="emergencyStop">Emergency stop</x-filament::button><x-filament::button color="gray" wire:click="clearEmergencyStop">Stop kaldır</x-filament::button></div>@endif
|
||||
</x-filament-panels::page>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="trafficpumper-admin-preferences" role="group" aria-label="{{ __('ui.preferences') }}">
|
||||
@include('filament.locale-picker')
|
||||
@include('filament.theme-picker')
|
||||
@auth
|
||||
@include('filament.user-menu')
|
||||
@endauth
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script>{!! file_get_contents(resource_path('js/theme-manager.js')) !!}</script>
|
||||
<script>
|
||||
try { localStorage.setItem('theme', window.ThemeManager.getPreference()); } catch (error) {}
|
||||
</script>
|
||||
<script>
|
||||
window.addEventListener('theme-changed', (event) => {
|
||||
if (window.ThemeManager) window.ThemeManager.setPreference(event.detail);
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<div
|
||||
x-data="{
|
||||
theme: localStorage.getItem('theme') || @js(filament()->getDefaultThemeMode()->value),
|
||||
labels: @js(['light' => __('ui.light'), 'dark' => __('ui.dark'), 'system' => __('ui.system')]),
|
||||
icons: { light: '☀', dark: '☾', system: '◐' },
|
||||
choose(value) {
|
||||
this.theme = value
|
||||
window.dispatchEvent(new CustomEvent('theme-changed', { detail: value }))
|
||||
},
|
||||
}"
|
||||
x-on:theme-changed.window="theme = $event.detail"
|
||||
>
|
||||
<x-filament::dropdown placement="bottom-end" teleport>
|
||||
<x-slot name="trigger">
|
||||
<button type="button" class="tp-admin-control" aria-label="{{ __('ui.theme') }}" title="{{ __('ui.theme') }}">
|
||||
<span class="tp-admin-theme-icon" aria-hidden="true" x-text="icons[theme] || icons.system">◐</span>
|
||||
<span class="tp-admin-control-label" x-text="labels[theme] || labels.system">{{ __('ui.system') }}</span>
|
||||
<x-filament::icon icon="heroicon-m-chevron-down" class="tp-admin-control-chevron" />
|
||||
</button>
|
||||
</x-slot>
|
||||
|
||||
<x-filament::dropdown.list>
|
||||
@foreach(['light' => ['heroicon-o-sun', __('ui.light')], 'dark' => ['heroicon-o-moon', __('ui.dark')], 'system' => ['heroicon-o-computer-desktop', __('ui.system')]] as $value => [$icon, $label])
|
||||
<button
|
||||
type="button"
|
||||
class="fi-dropdown-list-item"
|
||||
x-on:click="choose('{{ $value }}')"
|
||||
x-bind:aria-current="theme === '{{ $value }}' ? 'true' : null"
|
||||
>
|
||||
<x-filament::icon :icon="$icon" class="fi-icon fi-size-md" />
|
||||
<span class="fi-dropdown-list-item-label">{{ $label }}</span>
|
||||
<span class="ms-auto" x-show="theme === '{{ $value }}'" aria-hidden="true">✓</span>
|
||||
</button>
|
||||
@endforeach
|
||||
</x-filament::dropdown.list>
|
||||
</x-filament::dropdown>
|
||||
</div>
|
||||
@@ -0,0 +1,37 @@
|
||||
@php
|
||||
$user = auth()->user();
|
||||
$initials = collect(preg_split('/\s+/u', trim($user->name)) ?: [])
|
||||
->filter()
|
||||
->take(2)
|
||||
->map(fn (string $part) => mb_strtoupper(mb_substr($part, 0, 1)))
|
||||
->implode('');
|
||||
$profileUrl = $user->can('update', $user)
|
||||
? \App\Filament\Resources\Users\UserResource::getUrl('edit', ['record' => $user])
|
||||
: \App\Filament\Resources\Users\UserResource::getUrl();
|
||||
@endphp
|
||||
|
||||
<x-filament::dropdown placement="bottom-end" teleport>
|
||||
<x-slot name="trigger">
|
||||
<button type="button" class="tp-admin-user-control" aria-label="{{ __('filament-panels::layout.actions.open_user_menu.label') }}">
|
||||
<span class="tp-admin-avatar" aria-hidden="true">{{ $initials }}</span>
|
||||
<x-filament::icon icon="heroicon-m-chevron-down" class="tp-admin-user-chevron" />
|
||||
</button>
|
||||
</x-slot>
|
||||
|
||||
<x-filament::dropdown.header icon="heroicon-o-user-circle">
|
||||
<span class="tp-admin-user-name">{{ $user->name }}</span>
|
||||
<span class="tp-admin-user-email">{{ $user->email }}</span>
|
||||
</x-filament::dropdown.header>
|
||||
|
||||
<x-filament::dropdown.list>
|
||||
<x-filament::dropdown.list.item :href="$profileUrl" tag="a" icon="heroicon-o-user-circle">
|
||||
{{ __('customer.profile') }}
|
||||
</x-filament::dropdown.list.item>
|
||||
<form method="POST" action="{{ filament()->getLogoutUrl() }}">
|
||||
@csrf
|
||||
<x-filament::dropdown.list.item type="submit" icon="heroicon-o-arrow-left-on-rectangle">
|
||||
{{ __('ui.sign_out') }}
|
||||
</x-filament::dropdown.list.item>
|
||||
</form>
|
||||
</x-filament::dropdown.list>
|
||||
</x-filament::dropdown>
|
||||
@@ -0,0 +1,3 @@
|
||||
@csrf @if(($method??'POST')!=='POST')@method($method)@endif
|
||||
@if($bulk)<div class="mb-3"><label class="form-label" for="project">{{__('keywords.fields.project')}}</label><select id="project" name="project" class="form-select @error('project') is-invalid @enderror" required>@foreach($projects as $p)<option value="{{$p->ulid}}" @selected(old('project',$project->ulid)===$p->ulid)>{{$p->name}}</option>@endforeach</select>@error('project')<div class="invalid-feedback">{{$message}}</div>@enderror</div><div class="mb-3"><label class="form-label" for="phrases">{{__('keywords.fields.phrases')}}</label><textarea id="phrases" name="phrases" rows="8" class="form-control @error('phrases') is-invalid @enderror" required placeholder="{{__('keywords.placeholder')}}">{{old('phrases')}}</textarea><div class="form-text">{{__('keywords.bulk_limit',['count'=>100])}}</div>@error('phrases')<div class="invalid-feedback">{{$message}}</div>@enderror</div>@else<div class="mb-3"><label class="form-label" for="phrase">{{__('keywords.fields.phrase')}}</label><input id="phrase" name="phrase" maxlength="190" required class="form-control @error('phrase') is-invalid @enderror" value="{{old('phrase',$keyword->phrase)}}">@error('phrase')<div class="invalid-feedback">{{$message}}</div>@enderror</div>@endif
|
||||
<div class="row g-3 mb-4">@if($bulk)<div class="col-md-3"><label class="form-label">{{__('keywords.fields.engine')}}</label><select name="search_engine" class="form-select"><option value="google">Google</option></select></div>@endif<div class="col-md-3"><label class="form-label">{{__('keywords.fields.country')}}</label><select name="country_code" class="form-select">@foreach($countries as $v=>$l)<option value="{{$v}}" @selected(old('country_code',$keyword->country_code??'TR')===$v)>{{$l}}</option>@endforeach</select></div><div class="col-md-3"><label class="form-label">{{__('keywords.fields.language')}}</label><select name="language_code" class="form-select">@foreach($languages as $v=>$l)<option value="{{$v}}" @selected(old('language_code',$keyword->language_code??'tr')===$v)>{{$l}}</option>@endforeach</select></div><div class="col-md-3"><label class="form-label">{{__('keywords.fields.device')}}</label><select name="device" class="form-select">@foreach(\App\Enums\KeywordDevice::cases() as $v)<option value="{{$v->value}}" @selected(old('device',$keyword->device->value??'desktop')===$v->value)>{{$v->label()}}</option>@endforeach</select></div>@unless($bulk)<div class="col-md-3"><label class="form-label" for="status">{{__('keywords.status')}}</label><select id="status" name="status" required class="form-select @error('status') is-invalid @enderror"><option value="active" @selected(old('status',$keyword->status->value)==='active')>{{__('states.active')}}</option><option value="inactive" @selected(old('status',$keyword->status->value)==='inactive')>{{__('states.inactive')}}</option></select>@error('status')<div class="invalid-feedback">{{$message}}</div>@enderror</div>@endunless</div><button class="btn btn-primary">{{$submit}}</button> <a class="btn btn-link" href="{{route('keywords.index',['project'=>$project->ulid])}}">{{__('keywords.cancel')}}</a>
|
||||
@@ -0,0 +1 @@
|
||||
<x-layouts.app :title="__('keywords.add').' — TrafficPumper'"><x-customer-shell><header class="tp-page-header"><div><p>{{__('keywords.title')}}</p><h1>{{__('keywords.add')}}</h1><span>{{__('keywords.bulk_help')}}</span></div></header><section class="tp-panel tp-form-panel"><form method="POST" action="{{route('keywords.store')}}">@include('keywords._form',['submit'=>__('keywords.create'),'bulk'=>true])</form></section></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1 @@
|
||||
<x-layouts.app :title="__('keywords.edit').' — TrafficPumper'"><x-customer-shell><header class="tp-page-header"><div><p>{{$project->name}}</p><h1>{{__('keywords.edit')}}</h1></div></header><section class="tp-panel tp-form-panel"><form method="POST" action="{{route('keywords.update',$keyword)}}">@include('keywords._form',['submit'=>__('keywords.save'),'method'=>'PUT','bulk'=>false])</form></section></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,12 @@
|
||||
<x-layouts.app :title="__('keywords.title').' — TrafficPumper'"><x-customer-shell>
|
||||
<header class="tp-page-header"><div><p>{{__('customer.rank_tracking')}}</p><h1>{{__('keywords.title')}}</h1><span>{{__('keywords.description')}}</span></div><a class="btn btn-primary" href="{{route('keywords.create',['project'=>$projectUlid])}}">+ {{__('keywords.add')}}</a></header>
|
||||
@if(session('status'))<div class="alert alert-success">{{session('status')}}</div>@endif
|
||||
<section class="tp-metric-grid tp-keyword-metrics"><x-metric-card :label="__('keywords.metrics.total')" :value="$summary['total']" icon="⌕"/><x-metric-card :label="__('keywords.metrics.active')" :value="$summary['active']" icon="●"/><x-metric-card :label="__('customer.top10')" :value="$summary['top10']" icon="↗"/><x-metric-card :label="__('customer.average_rank')" :value="$summary['average']" icon="◎"/><x-metric-card :label="__('customer.rising')" :value="$summary['winners']" icon="↑"/><x-metric-card :label="__('customer.falling')" :value="$summary['losers']" icon="↓"/></section>
|
||||
<section class="tp-panel"><form method="GET" class="tp-keyword-filters"><input class="form-control" name="q" value="{{request('q')}}" placeholder="{{__('keywords.search')}}"><select class="form-select" name="project"><option value="">{{__('customer.all_projects')}}</option>@foreach($projects as $p)<option value="{{$p->ulid}}" @selected($projectUlid===$p->ulid)>{{$p->name}}</option>@endforeach</select><select class="form-select" name="status"><option value="" @selected(!request()->filled('status'))>{{__('keywords.view_all')}}</option><option value="active" @selected(request('status')==='active')>{{__('states.active')}}</option><option value="inactive" @selected(request('status')==='inactive')>{{__('states.inactive')}}</option><option value="archived" @selected(request('status')==='archived')>{{__('states.archived')}}</option></select><select class="form-select" name="country"><option value="">{{__('keywords.fields.country')}}</option>@foreach($countries as $v=>$l)<option value="{{$v}}" @selected(request('country')===$v)>{{$l}}</option>@endforeach</select><select class="form-select" name="device"><option value="">{{__('keywords.fields.device')}}</option>@foreach(\App\Enums\KeywordDevice::cases() as $v)<option value="{{$v->value}}" @selected(request('device')===$v->value)>{{$v->label()}}</option>@endforeach</select><button class="btn btn-outline-primary">{{__('keywords.filter')}}</button></form></section>
|
||||
<section class="tp-panel"><div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{__('keywords.keyword')}}</th><th>{{__('keywords.project')}}</th><th>{{__('keywords.current')}}</th><th>{{__('keywords.previous')}}</th><th>{{__('keywords.change')}}</th><th>{{__('keywords.target')}}</th><th>{{__('keywords.status')}}</th><th>{{__('keywords.last_check')}}</th><th>{{__('keywords.actions')}}</th></tr></thead><tbody>
|
||||
@forelse($keywords as $keyword)<tr><td><a href="{{route('keywords.show',$keyword)}}">{{$keyword->phrase}}</a></td><td>{{$keyword->project->name}}</td><td>{{$keyword->current_rank?'#'.$keyword->current_rank:'—'}}</td><td>{{$keyword->previous_rank?'#'.$keyword->previous_rank:'—'}}</td><td>@if($keyword->movement==='winner')<span class="tp-rank-up" aria-label="{{__('keywords.winner')}}">↑ +{{$keyword->rank_change}}</span>@elseif($keyword->movement==='loser')<span class="tp-rank-down" aria-label="{{__('keywords.loser')}}">↓ {{$keyword->rank_change}}</span>@elseif($keyword->movement==='unchanged')<span>= {{__('customer.unchanged')}}</span>@else<span>{{__('customer.new')}}</span>@endif</td><td>{{$keyword->search_engine->label()}} · {{$keyword->country_code}}/{{$keyword->language_code}} · {{$keyword->device->label()}}</td>
|
||||
<td>@if($keyword->status===\App\Enums\KeywordStatus::Archived)<span class="badge text-bg-secondary">{{__('states.archived')}}</span>@else<form method="POST" action="{{route('keywords.status',$keyword)}}" class="d-inline">@csrf @method('PATCH')<input type="hidden" name="status" value="{{$keyword->status===\App\Enums\KeywordStatus::Active?'inactive':'active'}}"><button class="btn btn-sm {{$keyword->status===\App\Enums\KeywordStatus::Active?'btn-success':'btn-outline-secondary'}}" aria-label="{{__('keywords.status')}}: {{$keyword->status->label()}}">{{$keyword->status===\App\Enums\KeywordStatus::Active?'●':'○'}} {{$keyword->status->label()}}</button></form>@endif</td>
|
||||
<td>{{$keyword->last_checked_at?->diffForHumans()??__('keywords.not_checked')}}</td><td><div class="d-flex gap-1">@if($keyword->status!==\App\Enums\KeywordStatus::Archived)<a class="btn btn-sm btn-outline-secondary" href="{{route('keywords.edit',$keyword)}}">{{__('keywords.edit')}}</a><form method="POST" action="{{route('keywords.archive',$keyword)}}" onsubmit="return confirm('{{__('keywords.archive_confirm')}}')">@csrf<button class="btn btn-sm btn-outline-danger">{{__('keywords.archive')}}</button></form>@else<form method="POST" action="{{route('keywords.restore',$keyword)}}">@csrf<button class="btn btn-sm btn-outline-primary">{{__('keywords.restore')}}</button></form>@endif</div></td></tr>
|
||||
@empty<tr><td colspan="9"><x-empty-state :title="__('keywords.empty')" :description="__('keywords.empty_help')"/></td></tr>@endforelse
|
||||
</tbody></table></div><div class="mt-3">{{$keywords->links()}}</div></section>
|
||||
</x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,4 @@
|
||||
<x-layouts.app :title="$keyword->phrase.' — TrafficPumper'"><x-customer-shell><nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="{{route('keywords.index',['project'=>$keyword->project->ulid])}}">{{__('keywords.title')}}</a></li><li class="breadcrumb-item active">{{$keyword->phrase}}</li></ol></nav><header class="tp-page-header"><div><p>{{$keyword->project->name}} · {{$keyword->project->domain}}</p><h1>{{$keyword->phrase}}</h1><span>{{$keyword->status->label()}}</span></div>@if($keyword->status===\App\Enums\KeywordStatus::Active)<a class="btn btn-outline-secondary" href="{{route('keywords.edit',$keyword)}}">{{__('keywords.edit')}}</a>@endif</header>
|
||||
<section class="tp-metric-grid tp-keyword-metrics"><x-metric-card :label="__('keywords.current')" :value="$keyword->current_rank?'#'.$keyword->current_rank:'—'" icon="◎"/><x-metric-card :label="__('keywords.previous')" :value="$keyword->previous_rank?'#'.$keyword->previous_rank:'—'" icon="◷"/><x-metric-card :label="__('keywords.change')" :value="$keyword->rank_change??__('customer.new')" icon="↕"/><x-metric-card :label="__('keywords.best')" :value="$best?'#'.$best:'—'" icon="★"/><x-metric-card :label="__('keywords.last_check')" :value="$keyword->last_checked_at?->diffForHumans()" icon="◴"/><x-metric-card :label="__('keywords.status')" :value="$keyword->status->label()" icon="●"/></section>
|
||||
<div class="tp-two-columns"><section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('keywords.details')}}</h2></div><dl class="tp-detail-list"><div><dt>{{__('keywords.project')}}</dt><dd>{{$keyword->project->name}}</dd></div><div><dt>{{__('keywords.domain')}}</dt><dd>{{$keyword->project->domain}}</dd></div><div><dt>{{__('keywords.target')}}</dt><dd>{{$keyword->search_engine->label()}} · {{$countries[$keyword->country_code]}} · {{$languages[$keyword->language_code]}} · {{$keyword->device->label()}}</dd></div><div><dt>{{__('keywords.created')}}</dt><dd>{{$keyword->created_at->translatedFormat('d M Y H:i')}}</dd></div></dl></section><section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('keywords.history')}}</h2></div>@if($history->isEmpty())<x-empty-state :title="__('customer.no_rank_data')" :description="__('customer.after_first_check')"/>@else<div class="tp-history-list">@foreach($history as $point)<div><time>{{\Illuminate\Support\Carbon::parse($point['date'])->translatedFormat('d M Y')}}</time><span style="--rank:{{min($point['rank'],100)}}"></span><strong>#{{$point['rank']}}</strong></div>@endforeach</div>@endif</section></div>
|
||||
<section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('keywords.recent_checks')}}</h2></div><div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{__('keywords.last_check')}}</th><th>{{__('keywords.current')}}</th><th>{{__('keywords.results')}}</th></tr></thead><tbody>@forelse($keyword->serpChecks->take(10) as $check)<tr><td>{{$check->checked_at?->translatedFormat('d M Y H:i')}}</td><td>{{$check->results->firstWhere('is_project_domain',true)?->position?'#'.$check->results->firstWhere('is_project_domain',true)->position:'100+'}}</td><td>{{$check->result_count}}</td></tr>@empty<tr><td colspan="3">{{__('keywords.not_checked')}}</td></tr>@endforelse</tbody></table></div></section></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,7 @@
|
||||
<x-layouts.app :title="__('profile.title')"><x-customer-shell><header class="tp-page-header"><div><p>{{__('customer.my_account')}}</p><h1>{{__('profile.title')}}</h1><span>{{__('profile.description')}}</span></div></header><div class="tp-profile-grid">
|
||||
<section class="tp-panel"><h2 class="h5">{{__('profile.information')}}</h2><form method="post" action="{{route('profile.update')}}">@csrf @method('PUT')<label class="form-label">{{__('profile.full_name')}}<input class="form-control" name="name" required maxlength="150" value="{{old('name',$u->name)}}"></label>@error('name')<p class="text-danger">{{$message}}</p>@enderror<button class="btn btn-primary">{{__('profile.save')}}</button></form></section>
|
||||
<section class="tp-panel"><h2 class="h5">{{__('profile.email_section')}}</h2><span class="badge {{$u->hasVerifiedEmail()?'text-bg-success':'text-bg-warning'}}">{{$u->hasVerifiedEmail()?__('profile.verified'):__('profile.not_verified')}}</span><form method="post" action="{{route('profile.email')}}" class="mt-3">@csrf @method('PUT')<label class="form-label">{{__('profile.email')}}<input class="form-control" type="email" name="email" required value="{{old('email',$u->email)}}"></label><label class="form-label">{{__('profile.current_password')}}<input class="form-control" type="password" name="current_password" required autocomplete="current-password"></label>@error('email')<p class="text-danger">{{$message}}</p>@enderror @error('current_password')<p class="text-danger">{{$message}}</p>@enderror<button class="btn btn-primary">{{__('profile.save')}}</button></form>@unless($u->hasVerifiedEmail())<form method="post" action="{{route('profile.verification.send')}}" class="mt-2">@csrf<button class="btn btn-outline-secondary">{{__('profile.resend')}}</button></form>@endunless</section>
|
||||
<section class="tp-panel"><h2 class="h5">{{__('profile.password_section')}}</h2><form method="post" action="{{route('profile.password')}}">@csrf @method('PUT')<label class="form-label">{{__('profile.current_password')}}<input class="form-control" type="password" name="current_password" required></label><label class="form-label">{{__('profile.new_password')}}<input class="form-control" type="password" name="password" required></label><label class="form-label">{{__('profile.confirm_password')}}<input class="form-control" type="password" name="password_confirmation" required></label>@error('password')<p class="text-danger">{{$message}}</p>@enderror<button class="btn btn-primary">{{__('profile.password_section')}}</button></form></section>
|
||||
<section class="tp-panel"><h2 class="h5">{{__('profile.roles_status')}}</h2><p>{{__('profile.roles')}}</p><div class="d-flex flex-wrap gap-2">@foreach($u->roles as $role)<span class="badge text-bg-secondary">{{__('roles.'.$role->name)}}</span>@endforeach</div><p class="mt-3">{{__('profile.status')}}: <strong>{{__('profile.'.($u->isActive()?'active':'inactive'))}}</strong></p>@if($u->canAccessPanel(filament()->getPanel('admin')))<a class="btn btn-outline-primary" href="/admin">{{__('profile.admin')}}</a>@endif</section>
|
||||
<section class="tp-panel tp-profile-sessions"><h2 class="h5">{{__('profile.sessions')}}</h2>@foreach($sessions as $session)<div class="tp-session-row"><span>{{$session['agent']?:'—'}}</span><small>{{$session['last_activity']}} @if($session['current'])· {{__('profile.current_device')}}@endif</small></div>@endforeach<form method="post" action="{{route('profile.sessions.logout-others')}}" class="mt-3">@csrf<label class="form-label">{{__('profile.current_password')}}<input class="form-control" type="password" name="current_password" required></label><button class="btn btn-outline-danger">{{__('profile.logout_others')}}</button></form></section>
|
||||
</div></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,4 @@
|
||||
@csrf @if(($method??'POST')!=='POST') @method($method) @endif
|
||||
<div class="mb-3"><label class="form-label" for="name">{{__('projects.project_name')}}</label><input class="form-control @error('name') is-invalid @enderror" id="name" name="name" maxlength="150" required value="{{old('name',$project->name??'')}}">@error('name')<div class="invalid-feedback">{{$message}}</div>@enderror</div>
|
||||
<div class="mb-4"><label class="form-label" for="website_url">{{__('projects.website_url')}}</label><input class="form-control @error('website_url') is-invalid @enderror" id="website_url" name="website_url" maxlength="2048" required placeholder="https://example.com" value="{{old('website_url',$project->url??'')}}"><div class="form-text">{{__('projects.url_help')}}</div>@error('website_url')<div class="invalid-feedback">{{$message}}</div>@enderror</div>
|
||||
<button class="btn btn-primary" type="submit">{{$submit}}</button><a class="btn btn-link" href="{{route('projects.index')}}">{{__('projects.cancel')}}</a>
|
||||
@@ -0,0 +1 @@
|
||||
<x-layouts.app :title="__('projects.new').' — TrafficPumper'"><x-customer-shell><nav aria-label="{{__('projects.breadcrumb')}}"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="{{route('projects.index')}}">{{__('customer.projects')}}</a></li><li class="breadcrumb-item active">{{__('projects.new')}}</li></ol></nav><header class="tp-page-header"><div><p>{{__('customer.rank_tracking')}}</p><h1>{{__('projects.new')}}</h1></div></header><section class="tp-panel tp-form-panel"><form method="POST" action="{{route('projects.store')}}">@include('projects._form',['submit'=>__('projects.create')])</form></section></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1 @@
|
||||
<x-layouts.app :title="__('projects.edit').' — TrafficPumper'"><x-customer-shell><nav aria-label="{{__('projects.breadcrumb')}}"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="{{route('projects.index')}}">{{__('customer.projects')}}</a></li><li class="breadcrumb-item"><a href="{{route('projects.show',$project)}}">{{$project->name}}</a></li><li class="breadcrumb-item active">{{__('projects.edit')}}</li></ol></nav><header class="tp-page-header"><div><p>{{__('customer.rank_tracking')}}</p><h1>{{__('projects.edit')}}</h1></div></header><section class="tp-panel tp-form-panel"><form method="POST" action="{{route('projects.update',$project)}}">@include('projects._form',['submit'=>__('projects.save'),'method'=>'PUT'])</form></section></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,2 @@
|
||||
<x-layouts.app :title="__('customer.projects').' — TrafficPumper'"><div class="tp-customer-shell"><x-customer-sidebar/><main class="tp-customer-main"><header class="tp-page-header"><div><p>{{ __('customer.rank_tracking') }}</p><h1>{{ __('customer.projects') }}</h1><span>{{ __('projects.intro') }}</span></div><a class="btn btn-primary" href="{{ route('projects.create') }}">+ {{ __('projects.new') }}</a></header>
|
||||
<div class="tp-panel"><ul class="nav nav-pills mb-3"><li class="nav-item"><a class="nav-link {{!$archived?'active':''}}" href="{{route('projects.index')}}">{{__('states.active')}}</a></li><li class="nav-item"><a class="nav-link {{$archived?'active':''}}" href="{{route('projects.index',['filter'=>'archived'])}}">{{__('states.archived')}}</a></li></ul><div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{__('projects.name')}}</th><th>{{__('projects.domain')}}</th><th>{{__('customer.tracked_keywords')}}</th><th>{{__('roles.fields.status')}}</th><th class="text-end">{{__('projects.actions')}}</th></tr></thead><tbody>@forelse($projects as $project)<tr><td><a class="fw-semibold" href="{{route('projects.show',$project)}}">{{$project->name}}</a></td><td>{{$project->domain}}</td><td>{{$project->keywords_count}}</td><td><span class="badge text-bg-{{$archived?'secondary':'success'}}">{{$project->status->label()}}</span></td><td class="text-end">@if($archived)<form class="d-inline" method="POST" action="{{route('projects.restore',$project)}}">@csrf<button class="btn btn-sm btn-outline-primary">{{__('projects.restore')}}</button></form>@else<a class="btn btn-sm btn-outline-secondary" href="{{route('projects.edit',$project)}}">{{__('projects.edit')}}</a> <form class="d-inline" method="POST" action="{{route('projects.archive',$project)}}" onsubmit="return confirm('{{__('projects.archive_confirm')}}')">@csrf<button class="btn btn-sm btn-outline-danger">{{__('projects.archive')}}</button></form>@endif</td></tr>@empty<tr><td colspan="5"><x-empty-state :title="__('projects.empty')" :description="__('projects.empty_hint')"/></td></tr>@endforelse</tbody></table></div></div><div>{{$projects->links()}}</div></main></div></x-layouts.app>
|
||||
@@ -0,0 +1,7 @@
|
||||
<x-layouts.app :title="$project->name.' — TrafficPumper'"><x-customer-shell class="tp-project-detail">
|
||||
<nav aria-label="{{ __('projects.breadcrumb') }}"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="{{ route('projects.index') }}">{{ __('customer.projects') }}</a></li><li class="breadcrumb-item active">{{ $project->name }}</li></ol></nav>
|
||||
<header class="tp-page-header"><div><p>{{ __('customer.rank_tracking') }}</p><h1>{{ $project->name }}</h1><span>{{ $project->domain }}</span></div><div class="d-flex flex-wrap gap-2"><span class="badge align-self-center text-bg-{{ $project->status===\App\Enums\ProjectStatus::Active?'success':'secondary' }}">{{ $project->status->label() }}</span>@if($project->status===\App\Enums\ProjectStatus::Active)<a class="btn btn-outline-secondary" href="{{route('projects.edit',$project)}}">{{__('projects.edit')}}</a><form method="POST" action="{{route('projects.archive',$project)}}" onsubmit="return confirm('{{__('projects.archive_confirm')}}')">@csrf<button class="btn btn-outline-danger">{{__('projects.archive')}}</button></form>@else<form method="POST" action="{{route('projects.restore',$project)}}">@csrf<button class="btn btn-primary">{{__('projects.restore')}}</button></form>@endif</div></header>
|
||||
<section class="tp-metric-grid tp-project-metrics"><x-metric-card :label="__('customer.tracked_keywords')" :value="$summary['keywords']" icon="⌕"/><x-metric-card :label="__('projects.last_serp_check')" :value="$summary['last_check']?->diffForHumans()" icon="◷"/><x-metric-card :label="__('customer.average_rank')" :value="$summary['average']" icon="◎"/><x-metric-card :label="__('customer.top10')" :value="$summary['top10']" icon="↗"/><x-metric-card :label="__('customer.rising')" :value="$summary['winners']" icon="↑"/><x-metric-card :label="__('customer.falling')" :value="$summary['losers']" icon="↓"/></section>
|
||||
<div class="tp-two-columns"><section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('projects.details')}}</h2></div><dl class="tp-detail-list"><div><dt>{{__('projects.root_url')}}</dt><dd><a href="{{$project->url}}" rel="noopener noreferrer">{{$project->url}}</a></dd></div><div><dt>{{__('roles.fields.status')}}</dt><dd>{{$project->status->label()}}</dd></div><div><dt>{{__('projects.created')}}</dt><dd>{{$project->created_at->translatedFormat('d M Y H:i')}}</dd></div><div><dt>{{__('projects.updated')}}</dt><dd>{{$project->updated_at->translatedFormat('d M Y H:i')}}</dd></div><div class="tp-technical-detail"><dt>{{__('projects.id')}}</dt><dd><code>{{$project->ulid}}</code></dd></div></dl></section><section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('customer.ranking_performance')}}</h2></div>@if($summary['average']===null)<x-empty-state :title="__('customer.no_rank_data')" :description="__('customer.after_first_check')"/>@else<div class="tp-rank-bars"><div><span>{{__('customer.average_rank')}}</span><i style="--rank:{{min($summary['average'],100)}}"></i><strong>#{{$summary['average']}}</strong></div></div>@endif</section></div>
|
||||
<section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('customer.keywords')}}</h2><div class="d-flex gap-2"><a class="btn btn-sm btn-outline-secondary" href="{{route('keywords.index',['project'=>$project->ulid])}}">{{__('keywords.view_all')}}</a><a class="btn btn-sm btn-primary" href="{{route('keywords.create',['project'=>$project->ulid])}}">+ {{__('projects.add_keyword')}}</a></div></div>@if($project->keywords->isEmpty())<x-empty-state :title="__('customer.no_keywords')" :description="__('customer.add_keyword_hint')"/>@else<div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{__('customer.keyword')}}</th><th>{{__('customer.target')}}</th><th>{{__('roles.fields.status')}}</th></tr></thead><tbody>@foreach($project->keywords as $keyword)<tr><td><a href="{{route('keywords.show',$keyword)}}">{{$keyword->phrase}}</a></td><td>{{$keyword->search_engine->label()}} · {{$keyword->device->label()}}</td><td>{{$keyword->status->label()}}</td></tr>@endforeach</tbody></table></div>@endif</section>
|
||||
</x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1 @@
|
||||
<x-layouts.app :title="__('rankings.details').' — TrafficPumper'"><x-customer-shell><nav><ol class="breadcrumb"><li class="breadcrumb-item"><a href="{{route('rankings.index')}}">{{__('rankings.title')}}</a></li><li class="breadcrumb-item"><a href="{{route('rankings.keywords.show',$check->keyword)}}">{{$check->keyword->phrase}}</a></li><li class="breadcrumb-item active">{{__('rankings.details')}}</li></ol></nav><header class="tp-page-header"><div><p>{{$check->keyword->project->name}}</p><h1>{{__('rankings.details')}}</h1><span>{{$check->checked_at?->translatedFormat('d M Y H:i')}}</span></div></header><div class="tp-two-columns"><section class="tp-panel"><dl class="tp-detail-list"><div><dt>{{__('rankings.keyword')}}</dt><dd>{{$check->keyword->phrase}}</dd></div><div><dt>{{__('rankings.project')}}</dt><dd>{{$check->keyword->project->name}}</dd></div><div><dt>{{__('rankings.rank')}}</dt><dd>{{$rank?'#'.$rank:'100+'}}</dd></div><div><dt>{{__('rankings.status')}}</dt><dd>{{$check->status->label()}}</dd></div><div><dt>{{__('rankings.country')}}</dt><dd>{{$check->country_code}} / {{$check->language_code}} / {{$check->device->label()}}</dd></div><div><dt>{{__('rankings.found_url')}}</dt><dd>{{$check->results->firstWhere('is_project_domain',true)?->url??'—'}}</dd></div></dl></section><section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('rankings.organic_results')}}</h2></div>@forelse($check->results as $result)<div class="tp-organic-result"><strong>#{{$result->position}} {{$result->title}}</strong><a href="{{$result->url}}" rel="noopener noreferrer">{{$result->display_url?:$result->url}}</a></div>@empty<x-empty-state :title="__('rankings.no_filter_data')" :description="__('rankings.no_checks')"/>@endforelse</section></div></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,8 @@
|
||||
<x-layouts.app :title="__('rankings.title').' — TrafficPumper'"><x-customer-shell>
|
||||
<header class="tp-page-header"><div><p>{{__('customer.rank_tracking')}}</p><h1>{{__('rankings.title')}}</h1><span>{{__('rankings.description')}}</span></div></header>
|
||||
@if($errors->any())<div class="alert alert-danger">{{$errors->first()}}</div>@endif
|
||||
<section class="tp-panel"><form method="GET" class="tp-ranking-filters"><select name="project" class="form-select"><option value="">{{__('rankings.all_projects')}}</option>@foreach($projects as $project)<option value="{{$project->ulid}}" @selected(request('project')===$project->ulid)>{{$project->name}}</option>@endforeach</select><select name="keywords[]" class="form-select"><option value="">{{__('rankings.all_keywords')}}</option>@foreach($keywords as $keyword)<option value="{{$keyword->ulid}}" @selected(in_array($keyword->ulid,request('keywords',[])))>{{$keyword->phrase}}</option>@endforeach</select><select name="range" class="form-select">@foreach(['7d'=>'last7','30d'=>'last30','90d'=>'last90','this_month'=>'this_month','last_month'=>'last_month','custom'=>'custom'] as $value=>$label)<option value="{{$value}}" @selected(request('range','30d')===$value)>{{__('rankings.'.$label)}}</option>@endforeach</select><input type="date" name="from" value="{{request('from')}}" class="form-control" aria-label="{{__('rankings.from')}}"><input type="date" name="to" value="{{request('to')}}" class="form-control" aria-label="{{__('rankings.to')}}"><select name="movement" class="form-select"><option value="">{{__('rankings.movement')}}</option>@foreach(['winner','loser','unchanged','new','unranked'] as $v)<option value="{{$v}}" @selected(request('movement')===$v)>{{__('rankings.'.$v)}}</option>@endforeach</select><button class="btn btn-primary">{{__('rankings.filters')}}</button><a class="btn btn-outline-secondary" href="{{route('rankings.index')}}">{{__('rankings.clear')}}</a></form></section>
|
||||
<section class="tp-metric-grid tp-ranking-metrics"><x-metric-card :label="__('rankings.tracked')" :value="$summary['tracked']" icon="⌕"/><x-metric-card :label="__('rankings.average')" :value="$summary['average']" icon="◎"/><x-metric-card :label="__('rankings.top3')" :value="$summary['top3']" icon="★"/><x-metric-card :label="__('rankings.top10')" :value="$summary['top10']" icon="↗"/><x-metric-card :label="__('rankings.winners')" :value="$summary['winners']" icon="↑"/><x-metric-card :label="__('rankings.losers')" :value="$summary['losers']" icon="↓"/></section>
|
||||
<div class="tp-ranking-grid"><section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('rankings.performance')}}</h2></div>@if($chart->isEmpty())<x-empty-state :title="__('rankings.no_checks')" :description="__('rankings.no_filter_data')"/>@else<div class="tp-ranking-chart" data-ranking-chart='@json($chart)' role="img" aria-label="{{__('rankings.performance')}}"></div><p class="small text-body-secondary mb-0">{{__('rankings.outside_help')}}</p>@endif</section><section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('rankings.distribution')}}</h2></div><div class="tp-distribution">@foreach($distribution as $key=>$count)<div><span>{{__('rankings.range.'.$key)}}</span><i style="--count:{{$count}}"></i><strong>{{$count}}</strong></div>@endforeach</div></section></div>
|
||||
<section class="tp-panel"><div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{__('rankings.date')}}</th><th>{{__('rankings.keyword')}}</th><th>{{__('rankings.project')}}</th><th>{{__('rankings.rank')}}</th><th>{{__('rankings.previous')}}</th><th>{{__('rankings.change')}}</th><th>{{__('rankings.engine')}}</th><th>{{__('rankings.country')}}</th><th>{{__('rankings.language')}}</th><th>{{__('rankings.device')}}</th><th>{{__('rankings.status')}}</th><th>{{__('rankings.action')}}</th></tr></thead><tbody>@forelse($rows as $row)<tr><td>{{$row['check']->checked_at->translatedFormat('d M Y H:i')}}</td><td><a href="{{route('rankings.keywords.show',$row['keyword'])}}">{{$row['keyword']->phrase}}</a></td><td>{{$row['project']->name}}</td><td>{{$row['rank']?'#'.$row['rank']:'100+'}}</td><td>{{$row['previous']?'#'.$row['previous']:'—'}}</td><td><span class="tp-movement tp-{{$row['movement']}}">{{__('rankings.'.$row['movement'])}} @if($row['change']!==null){{$row['change']>0?'+':''}}{{$row['change']}}@endif</span></td><td>{{$row['check']->search_engine->label()}}</td><td>{{$row['check']->country_code}}</td><td>{{$row['check']->language_code}}</td><td>{{$row['check']->device->label()}}</td><td>{{$row['check']->status->label()}}</td><td><a href="{{route('rankings.checks.show',$row['check'])}}">{{__('rankings.view')}}</a></td></tr>@empty<tr><td colspan="12"><x-empty-state :title="__('rankings.no_filter_data')" :description="__('rankings.no_checks')"/></td></tr>@endforelse</tbody></table></div>{{$rows->links()}}</section>
|
||||
</x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1 @@
|
||||
<x-layouts.app :title="$keyword->phrase.' — '.__('rankings.title')"><x-customer-shell><nav><ol class="breadcrumb"><li class="breadcrumb-item"><a href="{{route('rankings.index')}}">{{__('rankings.title')}}</a></li><li class="breadcrumb-item active">{{$keyword->phrase}}</li></ol></nav><header class="tp-page-header"><div><p>{{$keyword->project->name}} · {{$keyword->project->domain}}</p><h1>{{$keyword->phrase}}</h1><span>{{$keyword->search_engine->label()}} · {{$countries[$keyword->country_code]}} · {{$languages[$keyword->language_code]}} · {{$keyword->device->label()}}</span></div></header><section class="tp-metric-grid tp-ranking-metrics"><x-metric-card :label="__('rankings.current')" :value="$metrics['current']?'#'.$metrics['current']:'100+'" icon="◎"/><x-metric-card :label="__('rankings.previous')" :value="$metrics['previous']?'#'.$metrics['previous']:'—'" icon="◷"/><x-metric-card :label="__('rankings.best')" :value="$metrics['best']?'#'.$metrics['best']:'—'" icon="★"/><x-metric-card :label="__('rankings.worst')" :value="$metrics['worst']?'#'.$metrics['worst']:'—'" icon="↓"/><x-metric-card :label="__('rankings.measurements')" :value="$metrics['count']" icon="●"/><x-metric-card :label="__('rankings.top10_rate')" :value="$metrics['top10']!==null?$metrics['top10'].'%':null" icon="↗"/></section><section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('rankings.keyword_history')}}</h2></div><div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{__('rankings.date')}}</th><th>{{__('rankings.rank')}}</th><th>{{__('rankings.previous')}}</th><th>{{__('rankings.change')}}</th><th>{{__('rankings.status')}}</th><th>{{__('rankings.action')}}</th></tr></thead><tbody>@forelse($rows as $row)<tr><td>{{$row['check']->checked_at->translatedFormat('d M Y H:i')}}</td><td>{{$row['rank']?'#'.$row['rank']:'100+'}}</td><td>{{$row['previous']?'#'.$row['previous']:'—'}}</td><td>{{__('rankings.'.$row['movement'])}}</td><td>{{$row['check']->status->label()}}</td><td><a href="{{route('rankings.checks.show',$row['check'])}}">{{__('rankings.view')}}</a></td></tr>@empty<tr><td colspan="6">{{__('rankings.no_checks')}}</td></tr>@endforelse</tbody></table></div></section></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1 @@
|
||||
<x-layouts.app :title="$c->name"><x-customer-shell><header class="tp-page-header"><div><p>{{__('competitors.title')}}</p><h1>{{$c->name}}</h1><span>{{$c->normalized_domain}} · {{$p->name}}</span></div></header><div class="tp-metric-grid"><x-metric-card :label="__('competitors.your_visibility')" :value="number_format($data['scores'][$p->domain]??0,2).'%'"/><x-metric-card :label="__('competitors.domain')" :value="number_format($data['scores'][$c->normalized_domain]??0,2).'%'"/><x-metric-card :label="__('competitors.lead')" :value="$data['lead']"/><x-metric-card :label="__('competitors.trail')" :value="$data['trail']"/></div></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,4 @@
|
||||
<x-layouts.app :title="__('competitors.title')"><x-customer-shell><header class="tp-page-header"><div><p>{{__('customer.reports')}}</p><h1>{{__('competitors.title')}}</h1><span>{{__('competitors.description')}}</span></div></header>
|
||||
@if($projects->isEmpty())<section class="tp-panel">{{__('competitors.empty_projects')}}</section>@else<section class="tp-panel mb-3"><form method="get" class="d-flex gap-2"><select name="project" class="form-select">@foreach($projects as $p)<option value="{{$p->ulid}}" @selected($project?->is($p))>{{$p->name}}</option>@endforeach</select><button class="btn btn-primary">{{__('losers.filter')}}</button></form></section>
|
||||
<section class="tp-panel mb-3"><h2 class="h5">{{__('competitors.add')}}</h2><form method="post" action="{{route('competitors.store',$project)}}" class="tp-competitor-form">@csrf<input class="form-control" name="name" placeholder="{{__('competitors.name')}}"><input class="form-control" name="domain" required placeholder="{{__('competitors.domain')}}"><button class="btn btn-primary">{{__('competitors.add')}}</button></form>@error('domain')<p class="text-danger">{{$message}}</p>@enderror<table class="table mt-3"><tbody>@forelse($project->competitors as $c)<tr><td>{{$c->name}}</td><td>{{$c->normalized_domain}}</td><td>{{__('competitors.'.$c->status)}}</td><td><a href="{{route('competitors.show',[$project,$c])}}">{{__('competitors.view')}}</a></td></tr>@empty<tr><td>{{__('competitors.empty_competitors')}}</td></tr>@endforelse</tbody></table></section>
|
||||
@if($data&&$competitors->isNotEmpty())@php($strong=$competitors->sortByDesc(fn($c)=>$data['scores'][$c->normalized_domain]??0)->first())<div class="tp-metric-grid mb-3"><x-metric-card :label="__('competitors.your_visibility')" :value="number_format($data['scores'][$project->domain]??0,2).'%'"/><x-metric-card :label="__('competitors.strongest')" :value="$strong?->name"/><x-metric-card :label="__('competitors.lead')" :value="$data['lead']"/><x-metric-card :label="__('competitors.trail')" :value="$data['trail']"/><x-metric-card :label="__('competitors.shared')" :value="$data['shared']"/><x-metric-card :label="__('competitors.unranked')" :value="$data['unranked']"/></div><section class="tp-panel"><div class="table-responsive"><table class="table"><thead><tr><th>{{__('competitors.keyword')}}</th><th>{{__('competitors.your_rank')}}</th><th>{{__('competitors.competitor_rank')}}</th><th>{{__('competitors.gap')}}</th><th>{{__('competitors.leader')}}</th></tr></thead><tbody>@forelse($data['rows'] as $row)<tr><td>{{$row['keyword']->phrase}}</td><td>{{$row['own']??'—'}}</td><td>{{$row['rival']??'—'}}</td><td>{{$row['gap']??'—'}}</td><td>{{__('competitors.'.($row['leader']==='you'?'you_lead':($row['leader']==='competitor'?'competitor_leads':($row['leader']==='tied'?'tied':'no_measurement'))))}}</td></tr>@empty<tr><td colspan="5">{{__('competitors.empty_data')}}</td></tr>@endforelse</tbody></table></div></section>@endif @endif</x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,17 @@
|
||||
<x-layouts.app :title="__('losers.title')"><x-customer-shell>
|
||||
<header class="tp-page-header"><div><p>{{__('customer.reports')}}</p><h1>{{__('losers.title')}}</h1><span>{{__('losers.description')}}</span></div></header>
|
||||
<section class="tp-panel mb-3"><form method="get" class="tp-losers-filters">
|
||||
<input class="form-control" name="q" value="{{request('q')}}" placeholder="{{__('losers.search')}}">
|
||||
<select class="form-select" name="project"><option value="">{{__('losers.all_projects')}}</option>@foreach($projects as $p)<option value="{{$p->ulid}}" @selected(request('project')===$p->ulid)>{{$p->name}}</option>@endforeach</select>
|
||||
<select class="form-select" name="range">@foreach(['7d'=>'last7','30d'=>'last30','90d'=>'last90','this_month'=>'this_month','last_month'=>'last_month','custom'=>'custom'] as $v=>$label)<option value="{{$v}}" @selected(request('range','30d')===$v)>{{__('visibility.'.$label)}}</option>@endforeach</select>
|
||||
<select class="form-select" name="country"><option value="">{{__('visibility.country')}}</option>@foreach($countries as $v=>$label)<option value="{{$v}}" @selected(request('country')===$v)>{{$label}}</option>@endforeach</select>
|
||||
<select class="form-select" name="language"><option value="">{{__('visibility.language')}}</option>@foreach($languages as $v=>$label)<option value="{{$v}}" @selected(request('language')===$v)>{{$label}}</option>@endforeach</select>
|
||||
<select class="form-select" name="device"><option value="">{{__('visibility.device')}}</option>@foreach(\App\Enums\KeywordDevice::cases() as $v)<option value="{{$v->value}}" @selected(request('device')===$v->value)>{{$v->label()}}</option>@endforeach</select>
|
||||
<select class="form-select" name="engine"><option value="">{{__('visibility.engine')}}</option>@foreach(\App\Enums\SearchEngine::cases() as $v)<option value="{{$v->value}}" @selected(request('engine')===$v->value)>{{$v->label()}}</option>@endforeach</select>
|
||||
<input class="form-control" type="number" min="1" max="100" name="min_lost" value="{{request('min_lost')}}" placeholder="{{__('losers.min_lost')}}"><input class="form-control" type="date" name="from" value="{{request('from')}}"><input class="form-control" type="date" name="to" value="{{request('to')}}"><button class="btn btn-primary">{{__('losers.filter')}}</button></form></section>
|
||||
<div class="tp-metric-grid tp-losers-metrics mb-3"><x-metric-card :label="__('losers.ranking_losers')" :value="$summary['losers']" icon="↘"/><x-metric-card :label="__('losers.total_lost')" :value="$summary['lost']" icon="−"/><x-metric-card :label="__('losers.top3')" :value="$summary['top3']" icon="3"/><x-metric-card :label="__('losers.top10')" :value="$summary['top10']" icon="10"/><x-metric-card :label="__('losers.top100')" :value="$summary['top100']" icon="100"/><x-metric-card :label="__('losers.visibility_loss')" :value="$summary['visibility_loss']===null?null:number_format($summary['visibility_loss'],2).'%'" :description="$summary['visibility_loss']===null?__('losers.no_comparison'):null" icon="%"/></div>
|
||||
@if($critical->isNotEmpty())<section class="tp-panel tp-critical-losses mb-3"><h2 class="h5">{{__('losers.critical')}}</h2>@foreach($critical as $row)<a class="badge text-bg-danger text-decoration-none me-2" href="{{route('rankings.keywords.show',$row['keyword']->ulid)}}">{{$row['keyword']->phrase}} · {{__('losers.categories.'.$row['category'])}}</a>@endforeach</section>@endif
|
||||
<section class="tp-panel mb-3"><div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{__('losers.keyword')}}</th><th>{{__('losers.project')}}</th><th>{{__('losers.previous')}}</th><th>{{__('losers.current')}}</th><th>{{__('losers.lost')}}</th><th>{{__('losers.visibility_loss')}}</th><th>{{__('losers.category')}}</th><th>{{__('losers.target')}}</th><th>{{__('losers.device')}}</th><th>{{__('losers.last_check')}}</th><th></th></tr></thead><tbody>
|
||||
@forelse($rows as $row)<tr><td>{{$row['keyword']->phrase}}</td><td><a href="{{route('projects.show',$row['project']->ulid)}}">{{$row['project']->name}}</a></td><td>{{$row['previous']}}</td><td>{{$row['dropped_out']?__('losers.dropped_out_label'):$row['current']}}</td><td class="tp-loser">{{$row['lost']===null?'—':'−'.$row['lost']}}</td><td>{{number_format($row['visibility_loss'],2)}}%</td><td><span class="badge text-bg-danger">{{__('losers.categories.'.$row['category'])}}</span></td><td>{{$row['keyword']->country_code}} / {{$row['keyword']->language_code}}</td><td>{{$row['keyword']->device->label()}}</td><td>{{$row['checked_at']->format('d.m.Y H:i')}}</td><td><a href="{{route('rankings.keywords.show',$row['keyword']->ulid)}}">{{__('losers.view')}}</a></td></tr>@empty<tr><td colspan="11" class="text-center py-4 text-body-secondary">{{__('losers.empty')}}</td></tr>@endforelse</tbody></table></div>{{$rows->links()}}</section>
|
||||
<section class="tp-panel"><h2 class="h5">{{__('losers.distribution')}}</h2><div class="tp-distribution">@foreach($distribution as $key=>$count)<div><span>{{__('losers.ranges.'.$key)}}</span><i class="tp-loss-bar" style="--count:{{$count}}"></i><strong>{{$count}}</strong></div>@endforeach</div></section>
|
||||
</x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,14 @@
|
||||
<x-layouts.app :title="__('visibility.title').' — TrafficPumper'"><x-customer-shell>
|
||||
<header class="tp-page-header"><div><p>{{__('customer.reports')}}</p><h1>{{__('visibility.title')}}</h1><span>{{__('visibility.description')}}</span></div></header>
|
||||
<section class="tp-panel"><form method="GET" class="tp-visibility-filters">
|
||||
<select name="project" class="form-select"><option value="">{{__('visibility.all_projects')}}</option>@foreach($projects as $project)<option value="{{$project->ulid}}" @selected(request('project')===$project->ulid)>{{$project->name}}</option>@endforeach</select>
|
||||
<select name="range" class="form-select">@foreach(['7d'=>'last7','30d'=>'last30','90d'=>'last90','this_month'=>'this_month','last_month'=>'last_month','custom'=>'custom'] as $v=>$l)<option value="{{$v}}" @selected(request('range','30d')===$v)>{{__('visibility.'.$l)}}</option>@endforeach</select>
|
||||
<select name="compare" class="form-select"><option value="previous">{{__('visibility.compare_previous')}}</option><option value="none" @selected(request('compare')==='none')>{{__('visibility.no_compare')}}</option></select>
|
||||
<select name="country" class="form-select"><option value="">{{__('visibility.country')}}</option>@foreach($countries as $v=>$l)<option value="{{$v}}">{{$l}}</option>@endforeach</select>
|
||||
<select name="device" class="form-select"><option value="">{{__('visibility.device')}}</option>@foreach(\App\Enums\KeywordDevice::cases() as $v)<option value="{{$v->value}}">{{$v->label()}}</option>@endforeach</select>
|
||||
<input type="date" name="from" value="{{request('from')}}" class="form-control"><input type="date" name="to" value="{{request('to')}}" class="form-control"><button class="btn btn-primary">{{__('visibility.filter')}}</button><a class="btn btn-outline-secondary" href="{{route('reports.visibility')}}">{{__('visibility.clear')}}</a></form></section>
|
||||
<div class="alert alert-info"><strong>{{__('visibility.formula')}}</strong><br>{{__('visibility.coverage',['measured'=>$coverage[0],'total'=>$coverage[1]])}}@if($coverage[0]<$coverage[1])<br>{{__('visibility.coverage_warning')}}@endif</div>
|
||||
<section class="tp-metric-grid tp-visibility-metrics"><x-metric-card :label="__('visibility.score')" :value="$metrics['current']!==null?number_format($metrics['current'],2).'%':null" icon="◎"/><x-metric-card :label="__('visibility.previous')" :value="$metrics['previous']!==null?number_format($metrics['previous'],2).'%':__('visibility.no_comparison')" icon="◷"/><x-metric-card :label="__('visibility.change')" :value="$metrics['change']!==null?number_format($metrics['change'],2).'%':null" icon="↕"/><x-metric-card :label="__('visibility.measured')" :value="$metrics['measured']" icon="●"/><x-metric-card :label="__('visibility.top10')" :value="$metrics['top10']" icon="↗"/><x-metric-card :label="__('visibility.unranked')" :value="$metrics['unranked']" icon="—"/></section>
|
||||
<div class="tp-ranking-grid"><section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('visibility.trend')}}</h2></div>@if($trend->isEmpty())<x-empty-state :title="__('visibility.no_data')" :description="__('visibility.formula')"/>@else<div class="tp-visibility-chart" data-visibility-chart='@json(['current'=>$trend,'previous'=>$previousTrend])'></div>@endif</section><section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('visibility.distribution')}}</h2></div><div class="tp-distribution">@foreach($distribution as $key=>$count)<div><span>{{__('visibility.range_labels.'.$key)}}</span><i style="--count:{{$count}}"></i><strong>{{$count}}</strong></div>@endforeach</div></section></div>
|
||||
<div class="tp-two-columns">@foreach(['winners'=>$winners,'losers'=>$losers] as $title=>$items)<section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('visibility.'.$title)}}</h2></div><div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{__('visibility.keyword')}}</th><th>{{__('visibility.previous_rank')}}</th><th>{{__('visibility.current_rank')}}</th><th>{{__('visibility.point_change')}}</th></tr></thead><tbody>@forelse($items as $item)<tr><td><a href="{{route('rankings.keywords.show',$item['keyword'])}}">{{$item['keyword']->phrase}}</a><small class="d-block text-body-secondary">{{$item['project']->name}}</small></td><td>{{$item['previous']?'#'.$item['previous']:'100+'}}</td><td>{{$item['current']?'#'.$item['current']:'100+'}}</td><td class="{{$item['delta']>0?'tp-rank-up':'tp-rank-down'}}">{{$item['delta']>0?'+':''}}{{$item['delta']}}</td></tr>@empty<tr><td colspan="4">{{__('visibility.no_comparison')}}</td></tr>@endforelse</tbody></table></div></section>@endforeach</div>
|
||||
</x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,16 @@
|
||||
<x-layouts.app :title="__('winners.title')"><x-customer-shell>
|
||||
<div class="tp-page-heading"><div><h1>{{ __('winners.title') }}</h1><p>{{ __('winners.description') }}</p></div></div>
|
||||
<section class="tp-panel mb-3"><form method="get" action="{{ route('reports.winners') }}" class="tp-winners-filters">
|
||||
<label>{{ __('winners.search') }}<input class="form-control" name="q" value="{{ request('q') }}"></label>
|
||||
<label>{{ __('winners.project') }}<select class="form-select" name="project"><option value="">{{ __('winners.all_projects') }}</option>@foreach($projects as $project)<option value="{{ $project->ulid }}" @selected(request('project')===$project->ulid)>{{ $project->name }}</option>@endforeach</select></label>
|
||||
<label>{{ __('visibility.period') }}<select class="form-select" name="range">@foreach(['7d'=>'last7','30d'=>'last30','90d'=>'last90','this_month'=>'this_month','last_month'=>'last_month','custom'=>'custom'] as $range=>$label)<option value="{{ $range }}" @selected(request('range','30d')===$range)>{{ __('visibility.'.$label) }}</option>@endforeach</select></label>
|
||||
<label>{{ __('visibility.country') }}<select class="form-select" name="country"><option value="">—</option>@foreach($countries as $code=>$name)<option value="{{ $code }}" @selected(request('country')===$code)>{{ $name }}</option>@endforeach</select></label>
|
||||
<label>{{ __('visibility.language') }}<select class="form-select" name="language"><option value="">—</option>@foreach($languages as $code=>$name)<option value="{{ $code }}" @selected(request('language')===$code)>{{ $name }}</option>@endforeach</select></label>
|
||||
<label>{{ __('winners.min_gained') }}<input class="form-control" type="number" min="1" max="100" name="min_gained" value="{{ request('min_gained') }}"></label>
|
||||
<label>{{ __('visibility.from_date') }}<input class="form-control" type="date" name="from" value="{{ request('from') }}"></label><label>{{ __('visibility.to_date') }}<input class="form-control" type="date" name="to" value="{{ request('to') }}"></label>
|
||||
<div class="d-flex align-items-end"><button class="btn btn-primary w-100">{{ __('winners.filter') }}</button></div>
|
||||
</form></section>
|
||||
<div class="tp-metric-grid tp-winners-metrics mb-3"><x-metric-card :label="__('winners.ranking_winners')" :value="$summary['winners']" icon="↗"/><x-metric-card :label="__('winners.total_gained')" :value="$summary['gained']" icon="+"/><x-metric-card :label="__('winners.top3')" :value="$summary['top3']" icon="3"/><x-metric-card :label="__('winners.top10')" :value="$summary['top10']" icon="10"/><x-metric-card :label="__('winners.new_entries')" :value="$summary['new_entries']" icon="★"/><x-metric-card :label="__('winners.contribution')" :value="$summary['contribution']===null?null:number_format($summary['contribution'],2).'%'" :description="$summary['contribution']===null?__('winners.no_comparison'):null" icon="%"/></div>
|
||||
<section class="tp-panel mb-3"><div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{ __('winners.keyword') }}</th><th>{{ __('winners.project') }}</th><th>{{ __('winners.previous') }}</th><th>{{ __('winners.current') }}</th><th>{{ __('winners.gained') }}</th><th>{{ __('winners.visibility') }}</th><th>{{ __('winners.last_check') }}</th></tr></thead><tbody>@forelse($rows as $row)<tr><td><a href="{{ route('rankings.keywords.show',$row['keyword']->ulid) }}">{{ $row['keyword']->phrase }}</a><br><small class="text-body-secondary">{{ __('winners.categories.'.$row['category']) }}</small></td><td><a href="{{ route('projects.show',$row['project']->ulid) }}">{{ $row['project']->name }}</a></td><td>{{ $row['previous']??'—' }}</td><td>{{ $row['current']??'—' }}</td><td class="tp-winner">{{ $row['new_entry']?__('winners.categories.new_entry'):'+'.$row['gained'] }}</td><td>{{ number_format($row['contribution'],2) }}%</td><td>{{ $row['checked_at']->format('d.m.Y H:i') }}</td></tr>@empty<tr><td colspan="7" class="text-center text-body-secondary py-4">{{ __('winners.empty') }}</td></tr>@endforelse</tbody></table></div>{{ $rows->links() }}</section>
|
||||
<section class="tp-panel"><h2 class="h5">{{ __('winners.distribution') }}</h2><div class="tp-distribution">@foreach($distribution as $key=>$count)<div><span>{{ __('winners.ranges.'.$key) }}</span><i style="--count:{{ $count }}"></i><strong>{{ $count }}</strong></div>@endforeach</div></section>
|
||||
</x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,4 @@
|
||||
<x-layouts.app :title="__('serp_checks.title').' — TrafficPumper'"><x-customer-shell><header class="tp-page-header"><div><p>{{__('customer.rank_tracking')}}</p><h1>{{__('serp_checks.title')}}</h1><span>{{__('serp_checks.description')}}</span></div></header>@error('serp')<div class="alert alert-warning">{{$message}}</div>@enderror @if(!config('serp.enabled')||app(\App\Serp\Operations\ActivationManager::class)->allowsNormal()===false)<div class="alert alert-secondary">{{__('serp_checks.unavailable')}}</div>@endif
|
||||
<section class="tp-metric-grid tp-serp-metrics"><x-metric-card :label="__('serp_checks.total')" :value="$summary['total']" icon="⌕"/><x-metric-card :label="__('serp_checks.pending')" :value="$summary['pending']" icon="◷"/><x-metric-card :label="__('serp_checks.running')" :value="$summary['running']" icon="↻"/><x-metric-card :label="__('serp_checks.completed')" :value="$summary['completed']" icon="✓"/><x-metric-card :label="__('serp_checks.failed')" :value="$summary['failed']" icon="!"/><x-metric-card :label="__('serp_checks.last24')" :value="$summary['last24']" icon="◴"/></section>
|
||||
<section class="tp-panel"><form method="GET" class="tp-serp-filters"><input name="q" class="form-control" value="{{request('q')}}" placeholder="{{__('serp_checks.search')}}"><select name="project" class="form-select"><option value="">{{__('serp_checks.all_projects')}}</option>@foreach($projects as $project)<option value="{{$project->ulid}}" @selected(request('project')===$project->ulid)>{{$project->name}}</option>@endforeach</select><select name="keyword" class="form-select"><option value="">{{__('serp_checks.all_keywords')}}</option>@foreach($keywords as $keyword)<option value="{{$keyword->ulid}}" @selected(request('keyword')===$keyword->ulid)>{{$keyword->phrase}}</option>@endforeach</select><select name="status" class="form-select"><option value="">{{__('serp_checks.all_statuses')}}</option>@foreach(\App\Enums\SerpCheckStatus::cases() as $status)<option value="{{$status->value}}" @selected(request('status')===$status->value)>{{$status->label()}}</option>@endforeach</select><select name="device" class="form-select"><option value="">{{__('serp_checks.device')}}</option>@foreach(\App\Enums\KeywordDevice::cases() as $device)<option value="{{$device->value}}" @selected(request('device')===$device->value)>{{$device->label()}}</option>@endforeach</select><input type="date" name="from" value="{{request('from')}}" class="form-control"><input type="date" name="to" value="{{request('to')}}" class="form-control"><button class="btn btn-primary">{{__('serp_checks.filter')}}</button><a class="btn btn-outline-secondary" href="{{route('serp-checks.index')}}">{{__('serp_checks.clear')}}</a></form></section>
|
||||
<section class="tp-panel"><div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{__('serp_checks.keyword')}}</th><th>{{__('serp_checks.project')}}</th><th>{{__('serp_checks.type')}}</th><th>{{__('serp_checks.status')}}</th><th>{{__('serp_checks.rank')}}</th><th>{{__('serp_checks.device')}}</th><th>{{__('serp_checks.target')}}</th><th>{{__('serp_checks.requested')}}</th><th>{{__('serp_checks.completed_at')}}</th><th>{{__('serp_checks.action')}}</th></tr></thead><tbody>@forelse($checks as $check)<tr><td>{{$check->phrase}}</td><td>{{$check->keyword->project->name}}</td><td>{{__('serp_checks.'.($check->origin==='scheduled'?'scheduled':'manual'))}}</td><td><span class="badge tp-status-{{$check->status->value}}">{{$check->status->label()}}</span></td><td>{{$check->detected_rank?'#'.$check->detected_rank:($check->status===\App\Enums\SerpCheckStatus::Completed?'100+':'—')}}</td><td>{{$check->device->label()}}</td><td>{{$check->country_code}} / {{$check->language_code}}</td><td>{{$check->created_at->translatedFormat('d M Y H:i')}}</td><td>{{$check->completed_at?->translatedFormat('d M Y H:i')??'—'}}</td><td><a href="{{route('serp-checks.show',$check)}}">{{__('serp_checks.view')}}</a></td></tr>@empty<tr><td colspan="10"><x-empty-state :title="__('serp_checks.no_filter')" :description="__('serp_checks.no_checks')"/></td></tr>@endforelse</tbody></table></div>{{$checks->links()}}</section></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1 @@
|
||||
<x-layouts.app :title="__('serp_checks.details').' — TrafficPumper'"><x-customer-shell><nav><ol class="breadcrumb"><li class="breadcrumb-item"><a href="{{route('serp-checks.index')}}">{{__('serp_checks.title')}}</a></li><li class="breadcrumb-item active">{{$check->phrase}}</li></ol></nav><header class="tp-page-header"><div><p>{{$check->keyword->project->name}} · {{$check->keyword->project->domain}}</p><h1>{{__('serp_checks.details')}}</h1><span>{{$check->status->label()}}</span></div></header><section class="tp-metric-grid tp-serp-metrics"><x-metric-card :label="__('serp_checks.rank')" :value="$rank?'#'.$rank:($check->status===\App\Enums\SerpCheckStatus::Completed?'100+':null)" icon="◎"/><x-metric-card :label="__('serp_checks.previous')" :value="$previous?'#'.$previous:null" icon="◷"/><x-metric-card :label="__('serp_checks.change')" :value="$change" icon="↕"/><x-metric-card :label="__('serp_checks.status')" :value="$check->status->label()" icon="●"/><x-metric-card :label="__('serp_checks.type')" :value="__('serp_checks.'.($check->origin==='scheduled'?'scheduled':'manual'))" icon="⌁"/><x-metric-card :label="__('serp_checks.completed_at')" :value="$check->completed_at?->diffForHumans()" icon="✓"/></section><div class="tp-two-columns"><section class="tp-panel"><dl class="tp-detail-list"><div><dt>{{__('serp_checks.keyword')}}</dt><dd>{{$check->phrase}}</dd></div><div><dt>{{__('serp_checks.engine')}}</dt><dd>{{$check->search_engine->label()}}</dd></div><div><dt>{{__('serp_checks.target')}}</dt><dd>{{$check->country_code}} / {{$check->language_code}} / {{$check->device->label()}}</dd></div><div><dt>{{__('serp_checks.requested')}}</dt><dd>{{$check->created_at->translatedFormat('d M Y H:i')}}</dd></div><div><dt>{{__('serp_checks.started')}}</dt><dd>{{$check->started_at?->translatedFormat('d M Y H:i')??'—'}}</dd></div><div><dt>{{__('serp_checks.found_url')}}</dt><dd>{{$check->results->firstWhere('is_project_domain',true)?->url??'—'}}</dd></div>@if($check->status===\App\Enums\SerpCheckStatus::Failed)<div><dt>{{__('serp_checks.safe_error')}}</dt><dd>{{__('serp_checks.error.'.(in_array($check->error_code,['validation','timeout'],true)?$check->error_code:'failed'))}}</dd></div>@endif</dl></section><section class="tp-panel"><div class="tp-panel-heading"><h2>{{__('serp_checks.organic')}}</h2></div><div class="table-responsive"><table class="table tp-data-table"><thead><tr><th>{{__('serp_checks.result_rank')}}</th><th>{{__('serp_checks.title_col')}}</th><th>{{__('serp_checks.url')}}</th><th>{{__('serp_checks.ours')}}</th></tr></thead><tbody>@forelse($check->results as $result)<tr><td>#{{$result->position}}</td><td>{{$result->title}}</td><td>@if(in_array(parse_url($result->url,PHP_URL_SCHEME),['http','https'],true))<a href="{{$result->url}}" rel="noopener noreferrer">{{$result->display_url?:parse_url($result->url,PHP_URL_HOST)}}</a>@else{{$result->display_url}}@endif</td><td>{{$result->is_project_domain?__('serp_checks.yes'):__('serp_checks.no')}}</td></tr>@empty<tr><td colspan="4">{{__('serp_checks.no_checks')}}</td></tr>@endforelse</tbody></table></div></section></div></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1 @@
|
||||
<x-layouts.app :title="'SERP Geçmişi — '.$keyword->phrase"><div class="container-fluid"><div class="row"><x-customer-sidebar/><main class="col-lg-10 p-4 p-lg-5"><div class="d-flex flex-wrap justify-content-between gap-3 mb-4"><div><h1 class="h2">SERP kontrol geçmişi</h1><a href="{{ route('projects.keywords.show',[$project,$keyword]) }}">{{ $keyword->phrase }}</a></div>@if($keyword->status===\App\Enums\KeywordStatus::Active)@if(config('serp.enabled'))<form method="POST" action="{{ route('projects.keywords.serp-checks.store',[$project,$keyword]) }}">@csrf<button class="btn btn-primary">Kontrol başlat</button></form>@else<span class="alert alert-secondary py-2 mb-0">SERP kontrol hizmeti henüz yapılandırılmamış.</span>@endif @endif</div>@error('serp')<div class="alert alert-warning">{{ $message }}</div>@enderror<div class="card border-0 shadow-sm"><div class="table-responsive"><table class="table mb-0"><thead><tr><th>Zaman</th><th>Hedef</th><th>Durum</th><th>Sonuç</th></tr></thead><tbody>@forelse($checks as $check)<tr><td><a href="{{ route('projects.keywords.serp-checks.show',[$project,$keyword,$check]) }}">{{ $check->created_at->format('d.m.Y H:i') }}</a></td><td>{{ $check->search_engine->label() }} · {{ $check->country_code }}/{{ $check->language_code }} · {{ $check->device->label() }}</td><td>{{ $check->status->label() }}</td><td>{{ $check->status===\App\Enums\SerpCheckStatus::Completed?$check->result_count:'—' }}</td></tr>@empty<tr><td colspan="4" class="text-center py-5 text-body-secondary">Henüz gerçek kontrol kaydı yok.</td></tr>@endforelse</tbody></table></div></div><div class="mt-4">{{ $checks->links() }}</div></main></div></div></x-layouts.app>
|
||||
@@ -0,0 +1 @@
|
||||
<x-layouts.app title="SERP Kontrolü — TrafficPumper"><div class="container-fluid"><div class="row"><x-customer-sidebar/><main class="col-lg-10 p-4 p-lg-5"><h1 class="h2">SERP kontrolü</h1><p>{{ $check->phrase }} · {{ $check->status->label() }} · En iyi proje pozisyonu: {{ $best??'—' }}</p><div class="card border-0 shadow-sm"><div class="table-responsive"><table class="table mb-0"><thead><tr><th>Pozisyon</th><th>Sonuç</th><th>Proje</th></tr></thead><tbody>@forelse($check->results as $result)<tr><td>{{ $result->position }}</td><td><a href="{{ $result->url }}" rel="noopener noreferrer">{{ $result->title?:$result->display_url }}</a>@if($result->snippet)<div class="text-body-secondary">{{ $result->snippet }}</div>@endif</td><td>{{ $result->is_project_domain?'Evet':'Hayır' }}</td></tr>@empty<tr><td colspan="3" class="text-center py-5 text-body-secondary">Kaydedilmiş sonuç yok.</td></tr>@endforelse</tbody></table></div></div></main></div></div></x-layouts.app>
|
||||
@@ -0,0 +1,7 @@
|
||||
<x-layouts.app :title="__('settings.title')"><x-customer-shell><header class="tp-page-header"><div><p>{{__('customer.my_account')}}</p><h1>{{__('settings.title')}}</h1><span>{{__('settings.description')}}</span></div></header><div class="tp-settings-grid">
|
||||
<section class="tp-panel"><h2 class="h5">{{__('settings.appearance')}}</h2><form method="post" action="{{route('settings.appearance')}}">@csrf @method('PUT')<label class="form-label">{{__('settings.language')}}<select class="form-select" name="locale"><option value="tr" @selected(auth()->user()->locale==='tr')>Türkçe</option><option value="en" @selected(auth()->user()->locale==='en')>English</option></select></label><label class="form-label">{{__('settings.theme')}}<select class="form-select" name="theme" data-settings-theme>@foreach(['light','dark','system'] as $v)<option value="{{$v}}" @selected($setting->theme===$v)>{{__('settings.'.$v)}}</option>@endforeach</select></label><label class="form-check"><input type="hidden" name="reduced_motion" value="0"><input class="form-check-input" type="checkbox" name="reduced_motion" value="1" @checked($setting->reduced_motion)> {{__('settings.reduced_motion')}}</label><button class="btn btn-primary mt-3">{{__('settings.save')}}</button></form></section>
|
||||
<section class="tp-panel"><h2 class="h5">{{__('settings.regional')}}</h2><form method="post" action="{{route('settings.regional')}}">@csrf @method('PUT')<label class="form-label">{{__('settings.timezone')}}<select class="form-select" name="timezone">@foreach(DateTimeZone::listIdentifiers() as $zone)<option @selected($setting->timezone===$zone)>{{$zone}}</option>@endforeach</select></label><label class="form-label">{{__('settings.date_format')}}<select class="form-select" name="date_format">@foreach(['DD.MM.YYYY','YYYY-MM-DD','MM/DD/YYYY'] as $v)<option @selected($setting->date_format===$v)>{{$v}}</option>@endforeach</select></label><label class="form-label">{{__('settings.time_format')}}<select class="form-select" name="time_format"><option value="24" @selected($setting->time_format==='24')>24</option><option value="12" @selected($setting->time_format==='12')>12</option></select></label><button class="btn btn-primary">{{__('settings.save')}}</button></form></section>
|
||||
<section class="tp-panel"><h2 class="h5">{{__('settings.dashboard')}}</h2><form method="post" action="{{route('settings.dashboard')}}">@csrf @method('PUT')<label class="form-label">{{__('settings.default_project')}}<select class="form-select" name="default_project"><option value="">{{__('settings.all_projects')}}</option>@foreach($projects as $p)<option value="{{$p->ulid}}" @selected($setting->default_project_id===$p->id)>{{$p->name}}</option>@endforeach</select></label><label class="form-label">{{__('settings.default_range')}}<select class="form-select" name="default_report_range">@foreach(['7_days','30_days','90_days','this_month'] as $v)<option value="{{$v}}" @selected($setting->default_report_range===$v)>{{__('settings.ranges.'.$v)}}</option>@endforeach</select></label><label class="form-label">{{__('settings.per_page')}}<select class="form-select" name="per_page">@foreach([10,25,50,100] as $v)<option @selected($setting->per_page===$v)>{{$v}}</option>@endforeach</select></label><button class="btn btn-primary">{{__('settings.save')}}</button></form></section>
|
||||
<section class="tp-panel"><h2 class="h5">{{__('settings.notifications')}}</h2><form method="post" action="{{route('settings.notifications')}}">@csrf @method('PUT')<label class="form-check"><input type="hidden" name="in_app_notifications" value="0"><input class="form-check-input" type="checkbox" name="in_app_notifications" value="1" @checked($setting->in_app_notifications)> {{__('settings.in_app')}}</label><button class="btn btn-primary mt-3">{{__('settings.save')}}</button></form></section>
|
||||
<section class="tp-panel tp-settings-reset"><h2 class="h5">{{__('settings.reset')}}</h2><p>{{__('settings.reset_help')}}</p><form method="post" action="{{route('settings.reset')}}" onsubmit="return confirm('{{__('settings.reset')}}?')">@csrf<button class="btn btn-outline-danger">{{__('settings.reset')}}</button></form></section>
|
||||
</div></x-customer-shell></x-layouts.app>
|
||||
@@ -0,0 +1,54 @@
|
||||
@php($portal = app(\App\Support\PortalNavigationContext::class)->resolve(request()))
|
||||
<x-layouts.app :title="__('landing.meta_title')">
|
||||
<main class="tp-landing">
|
||||
<section class="tp-landing-hero">
|
||||
<div class="container">
|
||||
<div class="tp-landing-hero-copy">
|
||||
<span class="tp-landing-eyebrow">{{ __('landing.eyebrow') }}</span>
|
||||
<h1>{{ __('landing.hero_title') }}</h1>
|
||||
<p>{{ __('landing.hero_description') }}</p>
|
||||
<div class="tp-landing-actions">
|
||||
@if($portal['kind'] === 'web')
|
||||
<a class="btn btn-primary btn-lg" href="{{ $portal['panel_url'] }}">{{ $portal['panel_label'] }}</a>
|
||||
@else
|
||||
<a class="btn btn-primary btn-lg" href="{{ route('register') }}">{{ __('landing.create_account') }}</a>
|
||||
<a class="btn btn-outline-secondary btn-lg" href="{{ route('login') }}">{{ __('ui.sign_in') }}</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="tp-landing-preview" aria-label="{{ __('landing.preview_label') }}">
|
||||
<div class="tp-preview-heading"><span>{{ __('landing.preview_project') }}</span><strong>{{ __('landing.preview_visibility') }}</strong></div>
|
||||
<div class="tp-preview-metric"><span>{{ __('landing.preview_keywords') }}</span><strong>128</strong></div>
|
||||
<div class="tp-preview-chart" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
<div class="tp-preview-status"><span>● {{ __('landing.preview_gained') }}</span><span>● {{ __('landing.preview_stable') }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tp-landing-section" aria-labelledby="features-title">
|
||||
<div class="container">
|
||||
<div class="tp-section-heading"><span>{{ __('landing.features_eyebrow') }}</span><h2 id="features-title">{{ __('landing.features_title') }}</h2><p>{{ __('landing.features_description') }}</p></div>
|
||||
<div class="tp-feature-grid">
|
||||
@foreach(['keywords' => '◎', 'history' => '◷', 'serp' => '⌕', 'visibility' => '◒', 'movers' => '↕', 'competitors' => '◇', 'projects' => '▦', 'preferences' => '◐'] as $feature => $icon)
|
||||
<article class="tp-feature-card"><span class="tp-feature-icon" aria-hidden="true">{{ $icon }}</span><h3>{{ __('landing.features.'.$feature.'.title') }}</h3><p>{{ __('landing.features.'.$feature.'.description') }}</p></article>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tp-landing-section tp-how-section" aria-labelledby="how-title">
|
||||
<div class="container">
|
||||
<div class="tp-section-heading"><span>{{ __('landing.how_eyebrow') }}</span><h2 id="how-title">{{ __('landing.how_title') }}</h2></div>
|
||||
<ol class="tp-how-grid">
|
||||
@foreach(['project', 'keywords', 'track'] as $step)
|
||||
<li><span>{{ $loop->iteration }}</span><div><h3>{{ __('landing.steps.'.$step.'.title') }}</h3><p>{{ __('landing.steps.'.$step.'.description') }}</p></div></li>
|
||||
@endforeach
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tp-landing-cta"><div class="container"><div><h2>{{ __('landing.cta_title') }}</h2><p>{{ __('landing.cta_description') }}</p></div>@if($portal['kind'] === 'web')<a class="btn btn-primary btn-lg" href="{{ $portal['panel_url'] }}">{{ $portal['panel_label'] }}</a>@else<a class="btn btn-primary btn-lg" href="{{ route('register') }}">{{ __('landing.create_account') }}</a>@endif</div></section>
|
||||
</main>
|
||||
|
||||
<footer class="tp-landing-footer"><div class="container"><a href="{{ route('home') }}" class="tp-footer-brand">TrafficPumper</a><nav aria-label="{{ __('landing.footer_navigation') }}"><a href="{{ route('earn') }}">{{ __('earn.navigation') }}</a>@if($portal['kind'] === 'web')<a href="{{ $portal['panel_url'] }}">{{ $portal['panel_label'] }}</a>@else<a href="{{ route('login') }}">{{ __('ui.sign_in') }}</a><a href="{{ route('register') }}">{{ __('ui.register') }}</a>@endif</nav><small>© {{ now()->year }} TrafficPumper</small></div></footer>
|
||||
</x-layouts.app>
|
||||
Reference in New Issue
Block a user