This commit is contained in:
Codex
2026-07-10 11:18:07 -05:00
parent 42db078e47
commit e5a95a0687
32 changed files with 471 additions and 11 deletions
+5
View File
@@ -1,11 +1,16 @@
<x-app-layout>
<x-slot name="header"><h2 class="text-xl font-semibold">Buyers</h2></x-slot>
<div class="mx-auto max-w-7xl space-y-4 p-6">
<form method="GET" action="{{ route('buyers.index') }}" class="flex gap-3 rounded border bg-white p-4">
<input name="q" value="{{ request('q') }}" placeholder="Search buyers..." class="w-full rounded-md border-gray-300">
<x-primary-button>Search</x-primary-button>
</form>
@foreach ($profiles as $profile)
<a href="{{ route('buyers.show', $profile) }}" class="block rounded border bg-white p-4 shadow-sm">
<h3 class="font-semibold">{{ $profile->company_name }}</h3>
<p class="text-sm text-gray-600">{{ $profile->headline }}</p>
<p class="mt-2 text-sm">{{ $profile->hiring_regions }}</p>
<x-rating-summary :ratings="$profile->ratings" />
</a>
@endforeach
{{ $profiles->links() }}
+2
View File
@@ -10,5 +10,7 @@
<div><dt class="text-sm text-gray-500">Payment terms</dt><dd class="whitespace-pre-line">{{ $profile->payment_terms }}</dd></div>
</dl>
<x-attachments :attachments="$profile->attachments" />
<x-rating-summary :ratings="$profile->ratings" />
<x-rating-form type="buyer_profile" :id="$profile->id" category="buyer_overall" />
</div>
</x-app-layout>
@@ -0,0 +1,21 @@
@props(['type', 'id', 'category' => 'overall', 'mode' => 'stars'])
@auth
<form method="POST" action="{{ route('ratings.store') }}" class="mt-4 space-y-3 rounded border p-4">
@csrf
<input type="hidden" name="rateable_type" value="{{ $type }}">
<input type="hidden" name="rateable_id" value="{{ $id }}">
<input type="hidden" name="category" value="{{ $category }}">
@if ($mode === 'thumbs')
<label class="block text-sm font-medium">Vote</label>
<select name="thumbs_up" class="rounded-md border-gray-300">
<option value="1">Thumbs up</option>
<option value="0">Thumbs down</option>
</select>
@else
<x-field name="stars" label="Stars 1-5" type="number" />
@endif
<x-field name="body" label="Reason" textarea />
<x-primary-button>Save rating</x-primary-button>
</form>
@endauth
@@ -0,0 +1,25 @@
@props(['ratings'])
@php
$starRatings = $ratings->whereNotNull('stars');
$thumbRatings = $ratings->whereNotNull('thumbs_up');
$average = $starRatings->count() ? round($starRatings->avg('stars'), 1) : null;
$up = $thumbRatings->where('thumbs_up', true)->count();
$down = $thumbRatings->where('thumbs_up', false)->count();
@endphp
<div class="mt-4 rounded border bg-gray-50 p-4 text-sm">
<p class="font-semibold">Community rating</p>
<p class="mt-1 text-gray-700">
Stars: {{ $average ? $average.'/5' : 'n/a' }} from {{ $starRatings->count() }} vote{{ $starRatings->count() === 1 ? '' : 's' }}
| Thumbs: {{ $up }} up / {{ $down }} down
</p>
@foreach ($ratings->take(5) as $rating)
@php
$value = $rating->stars
? $rating->stars.'/5'
: ($rating->thumbs_up === null ? 'no vote' : ($rating->thumbs_up ? 'thumbs up' : 'thumbs down'));
@endphp
<p class="mt-2 text-gray-600">{{ $rating->user->name ?? 'User' }} | {{ $rating->category }} | {{ $value }} {{ $rating->body ? '- '.$rating->body : '' }}</p>
@endforeach
</div>
+15
View File
@@ -11,9 +11,24 @@
<x-attachment-form type="dispute" :id="$dispute->id" kind="evidence" />
<h3 class="mt-4 font-semibold">Recommended resolution</h3>
<p class="mt-2 whitespace-pre-line">{{ $dispute->recommended_resolution ?? 'Pending peer review.' }}</p>
<x-rating-summary :ratings="$dispute->ratings" />
<x-rating-form type="dispute" :id="$dispute->id" category="resolution_fairness" mode="thumbs" />
</section>
<section class="rounded border bg-white p-6">
<h3 class="font-semibold">Peer votes</h3>
@php
$voteGroups = $dispute->votes->groupBy('recommendation')->map->count();
$voteTotal = max($dispute->votes->count(), 1);
@endphp
<div class="mt-3 grid gap-3 md:grid-cols-4">
@foreach (['provider' => 'Provider', 'buyer' => 'Buyer', 'split' => 'Split', 'insufficient_evidence' => 'Insufficient evidence'] as $key => $label)
@php $count = $voteGroups[$key] ?? 0; @endphp
<div class="rounded border bg-gray-50 p-3 text-sm">
<p class="font-semibold">{{ $label }}</p>
<p>{{ $count }} vote{{ $count === 1 ? '' : 's' }} | {{ round(($count / $voteTotal) * 100) }}%</p>
</div>
@endforeach
</div>
<form method="POST" action="{{ route('dispute-votes.store', $dispute) }}" class="mt-4 space-y-4">
@csrf
<label class="block text-sm font-medium">Recommendation</label>
+11
View File
@@ -1,12 +1,23 @@
<x-app-layout>
<x-slot name="header"><h2 class="text-xl font-semibold">Jobs</h2></x-slot>
<div class="mx-auto max-w-7xl space-y-4 p-6">
<form method="GET" action="{{ route('jobs.index') }}" class="grid gap-3 rounded border bg-white p-4 md:grid-cols-3">
<input name="q" value="{{ request('q') }}" placeholder="Search jobs..." class="rounded-md border-gray-300">
<select name="status" class="rounded-md border-gray-300">
<option value="">Any status</option>
@foreach (['open', 'assigned', 'closed', 'cancelled'] as $status)
<option value="{{ $status }}" @selected(request('status') === $status)>{{ $status }}</option>
@endforeach
</select>
<x-primary-button>Search</x-primary-button>
</form>
@auth @role('buyer')<a class="rounded bg-indigo-600 px-4 py-2 text-white" href="{{ route('jobs.create') }}">Post job</a>@endrole @endauth
@foreach ($jobs as $job)
<a href="{{ route('jobs.show', $job) }}" class="block rounded border bg-white p-4 shadow-sm">
<h3 class="font-semibold">{{ $job->title }}</h3>
<p class="text-sm text-gray-600">{{ $job->location }} | {{ $job->service_category }} | {{ $job->status }}</p>
<p class="mt-2 line-clamp-2 text-sm">{{ $job->scope }}</p>
<x-rating-summary :ratings="$job->ratings" />
</a>
@endforeach
{{ $jobs->links() }}
+2
View File
@@ -11,6 +11,8 @@
<div><dt class="text-sm text-gray-500">Payment terms</dt><dd class="whitespace-pre-line">{{ $job->payment_terms }}</dd></div>
</dl>
<x-attachments :attachments="$job->attachments" />
<x-rating-summary :ratings="$job->ratings" />
<x-rating-form type="job_post" :id="$job->id" category="job_quality" />
@auth
@if (auth()->id() === $job->buyer_id)
<x-attachment-form type="job_post" :id="$job->id" kind="job" />
@@ -30,6 +30,14 @@
<x-nav-link :href="route('work-orders.index')" :active="request()->routeIs('work-orders.*')">
{{ __('Work Orders') }}
</x-nav-link>
@auth
<x-nav-link :href="route('notifications.index')" :active="request()->routeIs('notifications.*')">
{{ __('Notifications') }}
@if (auth()->user()->unreadNotifications()->count())
({{ auth()->user()->unreadNotifications()->count() }})
@endif
</x-nav-link>
@endauth
@role('admin')
<x-nav-link :href="route('admin.index')" :active="request()->routeIs('admin.*')">
{{ __('Admin') }}
@@ -122,6 +130,11 @@
<x-responsive-nav-link :href="route('work-orders.index')" :active="request()->routeIs('work-orders.*')">
{{ __('Work Orders') }}
</x-responsive-nav-link>
@auth
<x-responsive-nav-link :href="route('notifications.index')" :active="request()->routeIs('notifications.*')">
{{ __('Notifications') }}
</x-responsive-nav-link>
@endauth
@auth
@role('admin')
<x-responsive-nav-link :href="route('admin.index')" :active="request()->routeIs('admin.*')">
@@ -0,0 +1,27 @@
<x-app-layout>
<x-slot name="header"><h2 class="text-xl font-semibold">Notifications</h2></x-slot>
<div class="mx-auto max-w-5xl space-y-4 p-6">
<form method="POST" action="{{ route('notifications.read-all') }}">
@csrf
<x-secondary-button>Mark all read</x-secondary-button>
</form>
@foreach ($notifications as $notification)
<div class="rounded border bg-white p-4 {{ $notification->read_at ? '' : 'border-indigo-400' }}">
<p class="font-semibold">{{ $notification->data['title'] ?? 'Notification' }}</p>
<p class="mt-1 text-sm text-gray-600">{{ $notification->data['body'] ?? '' }}</p>
<div class="mt-3 flex gap-3 text-sm">
@if (! empty($notification->data['url']))
<a class="text-indigo-600" href="{{ $notification->data['url'] }}">Open</a>
@endif
@unless ($notification->read_at)
<form method="POST" action="{{ route('notifications.read', $notification->id) }}">
@csrf
<button class="text-gray-600" type="submit">Mark read</button>
</form>
@endunless
</div>
</div>
@endforeach
{{ $notifications->links() }}
</div>
</x-app-layout>
@@ -1,11 +1,16 @@
<x-app-layout>
<x-slot name="header"><h2 class="text-xl font-semibold">Providers</h2></x-slot>
<div class="mx-auto max-w-7xl space-y-4 p-6">
<form method="GET" action="{{ route('providers.index') }}" class="flex gap-3 rounded border bg-white p-4">
<input name="q" value="{{ request('q') }}" placeholder="Search providers..." class="w-full rounded-md border-gray-300">
<x-primary-button>Search</x-primary-button>
</form>
@foreach ($profiles as $profile)
<a href="{{ route('providers.show', $profile) }}" class="block rounded border bg-white p-4 shadow-sm">
<h3 class="font-semibold">{{ $profile->business_name }}</h3>
<p class="text-sm text-gray-600">{{ $profile->headline }}</p>
<p class="mt-2 text-sm">{{ $profile->service_area }}</p>
<x-rating-summary :ratings="$profile->ratings" />
@foreach ($profile->externalImports as $import)
<span class="mt-2 inline-block rounded bg-gray-100 px-2 py-1 text-xs">{{ $import->platform }} {{ $import->rating ? $import->rating.'/5' : '' }}</span>
@endforeach
+2
View File
@@ -13,6 +13,8 @@
<div><dt class="text-sm text-gray-500">Travel policy</dt><dd class="whitespace-pre-line">{{ $profile->travel_policy }}</dd></div>
</dl>
<x-attachments :attachments="$profile->attachments" />
<x-rating-summary :ratings="$profile->ratings" />
<x-rating-form type="provider_profile" :id="$profile->id" category="provider_overall" />
</section>
<section class="rounded border bg-white p-6">
<h3 class="font-semibold">External profile history</h3>
+15
View File
@@ -0,0 +1,15 @@
<x-app-layout>
<x-slot name="header"><h2 class="text-xl font-semibold">Platform Terms</h2></x-slot>
<div class="mx-auto max-w-4xl space-y-6 p-6">
<section class="rounded border bg-white p-6">
<h3 class="font-semibold">Core guardrails</h3>
<ul class="mt-4 list-disc space-y-2 pl-6 text-sm text-gray-700">
<li>The platform does not hold money or provide escrow in this version.</li>
<li>The platform does not set, recommend, or enforce market rates.</li>
<li>Providers and buyers negotiate directly and remain independent businesses.</li>
<li>Ratings, reviews, votes, and dispute records are community reputation signals, not legal judgments.</li>
<li>Dispute voting is intended to be transparent, democratic, and reviewable by the community.</li>
</ul>
</section>
</div>
</x-app-layout>
@@ -5,6 +5,7 @@
<a href="{{ route('work-orders.show', $order) }}" class="block rounded border bg-white p-4 shadow-sm">
<h3 class="font-semibold">{{ $order->jobPost->title }}</h3>
<p class="text-sm text-gray-600">{{ $order->status }} | Buyer: {{ $order->buyer->name }} | Provider: {{ $order->provider->name }}</p>
<x-rating-summary :ratings="$order->ratings" />
</a>
@endforeach
{{ $orders->links() }}
@@ -7,6 +7,8 @@
<h3 class="mt-6 font-semibold">Deliverables</h3>
<p class="mt-2 whitespace-pre-line">{{ $workOrder->deliverables_checklist }}</p>
<x-attachments :attachments="$workOrder->attachments" />
<x-rating-summary :ratings="$workOrder->ratings" />
<x-rating-form type="work_order" :id="$workOrder->id" category="work_order_outcome" mode="thumbs" />
<x-attachment-form type="work_order" :id="$workOrder->id" kind="work_order" />
<form method="POST" action="{{ route('work-orders.transition', $workOrder) }}" class="mt-6 space-y-4">
@csrf @method('PATCH')