download fixes

This commit is contained in:
Mark Gregory
2026-10-12 10:54:23 +01:00
parent 9c3353924b
commit f9123813fb
5 changed files with 48 additions and 1 deletions
@@ -38,6 +38,12 @@ final class ReleaseController extends Controller
return $this->send($release);
}
public function productDetails(DownloadProduct $product)
{
abort_unless($product->is_published && $product->file_path, 404);
return view('downloads.product', compact('product'));
}
public function productDownload(DownloadProduct $product): BinaryFileResponse
{
abort_unless($product->is_published && $product->file_path && Storage::disk('local')->exists($product->file_path), 404);
+7 -1
View File
@@ -4,7 +4,13 @@
<section class="download-confidence"><div class="download-shell confidence-grid"><div><span>Native macOS</span><strong>Fast, focused desktop workflow</strong></div><div><span>SonarQube</span><strong>Your server remains the source of truth</strong></div><div><span>Security</span><strong>Tokens protected by macOS Keychain</strong></div><div><span>Updates</span><strong>Professional in-app release delivery</strong></div></div></section>
<div class="download-shell download-content">@if($latest)<section id="release-notes" class="release-notes-panel"><div class="release-heading"><div><span class="eyebrow">Latest stable release</span><h2>SonarDesk {{ $latest->version }}</h2><p>Build {{ $latest->build_number }} · Released {{ $latest->published_at?->format('j F Y') }}</p></div><a class="btn btn-primary" href="{{ route('download.release',$latest) }}">Download {{ $latest->version }}</a></div><div class="release-note-copy">{!! nl2br(e($latest->release_notes ?: 'This release contains the latest SonarDesk improvements and maintenance updates.')) !!}</div><details class="checksum-box"><summary>SHA-256 checksum · Verify download</summary><code>{{ $latest->sha256 }}</code></details></section>@endif<aside class="requirements-panel"><span class="eyebrow">Before you install</span><h2>System requirements</h2><p>{{ $settings['requirements'] }}</p><ol><li><span>1</span><div><strong>Download</strong><small>Get the latest signed SonarDesk package.</small></div></li><li><span>2</span><div><strong>Install</strong><small>Open the DMG and move SonarDesk to Applications.</small></div></li><li><span>3</span><div><strong>Activate</strong><small>Enter the licence key delivered after purchase.</small></div></li></ol><a href="{{ route('buy') }}">Need a licence? View pricing →</a></aside></div>
@if($products->isNotEmpty())
<section class="distribution-catalogue"><div class="download-shell"><div class="section-heading"><span class="eyebrow">Software catalogue</span><h2>More downloads</h2><p>Additional tools and installers available from our team.</p></div><div class="distribution-product-grid">@foreach($products as $product)<article class="distribution-product-card"><div class="distribution-product-top"><span class="distribution-platform">{{ $product->platform ?: 'Software' }}</span><span class="distribution-version">{{ $product->version ? 'v'.$product->version : 'Latest' }}</span></div><div class="distribution-product-icon" aria-hidden="true">↓</div><h3>{{ $product->name }}</h3><div class="distribution-product-description">{!! \Illuminate\Support\Str::markdown($product->description ?: 'Download the latest available package.', ['html_input' => 'strip', 'allow_unsafe_links' => false]) !!}</div><div class="distribution-product-footer"><span>{{ $product->formatted_size }}</span><a class="btn btn-primary" href="{{ route('download.product',$product) }}" aria-label="Download {{ $product->name }}">Download →</a></div></article>@endforeach</div></div></section>
<section class="distribution-catalogue"><div class="download-shell"><div class="section-heading"><span class="eyebrow">Software catalogue</span><h2>More downloads</h2><p>Additional tools and installers available from our team.</p></div><div class="distribution-product-grid">@foreach($products as $product)
<article class="distribution-product-card">
<div class="distribution-product-top"><span class="distribution-platform">{{ $product->platform ?: 'Software' }}</span><span class="distribution-version">{{ $product->version ? 'v'.$product->version : 'Latest' }}</span></div>
<h3><a href="{{ route('download.product.details', $product) }}" style="color:inherit;text-decoration:none">{{ $product->name }}</a></h3>
<p class="distribution-product-description">{{ \Illuminate\Support\Str::limit(trim(preg_replace('/\s+/', ' ', strip_tags(\Illuminate\Support\Str::markdown($product->description ?: 'Download the latest available package.', ['html_input' => 'strip', 'allow_unsafe_links' => false])))), 190) }}</p>
<div class="distribution-product-footer"><span>{{ $product->formatted_size }}</span><div class="distribution-product-links"><a class="product-details-link" href="{{ route('download.product.details', $product) }}">View details →</a><a class="btn btn-primary" href="{{ route('download.product',$product) }}" aria-label="Download {{ $product->name }}">Download ↓</a></div></div>
</article>@endforeach</div></div></section>
@endif
<section class="release-history"><div class="download-shell"><div class="section-heading"><span class="eyebrow">Release archive</span><h2>Previous versions</h2><p>Published SonarDesk builds remain available here for customers who need a previous release.</p></div><div class="release-history-list">@forelse($releases as $release)<article><div><strong>SonarDesk {{ $release->version }}</strong><span>Build {{ $release->build_number }} · {{ ucfirst($release->channel) }} · {{ $release->published_at?->format('j M Y') }}</span></div><div><span>{{ $release->formatted_size }}</span><a href="{{ route('download.release',$release) }}">Download</a></div></article>@empty<p>No published releases are available yet.</p>@endforelse</div>{{ $releases->links() }}</div></section>
<section class="download-support"><div class="download-shell"><div><span class="eyebrow">Support</span><h2>Installation or activation help?</h2><p>{{ $settings['support_text'] }}</p></div><a class="btn btn-secondary" href="{{ route('support') }}">Contact SonarDesk support</a></div></section></main>
@@ -0,0 +1,13 @@
@extends('layouts.app', ['title' => $product->name.' — Downloads', 'description' => 'Download '.$product->name.' and read product information and release notes.'])
@section('fullbleed')
<main class="download-page"><div class="download-shell">
<article class="product-detail-panel">
<a href="{{ route('release') }}" style="color:#175cd3;text-decoration:none;font-weight:700">← All downloads</a>
<div class="distribution-product-top" style="margin-top:26px"><span class="distribution-platform">{{ $product->platform ?: 'Software' }}</span><span class="distribution-version">{{ $product->version ? 'v'.$product->version : 'Latest' }}</span></div>
<h1>{{ $product->name }}</h1>
<p style="color:#667085">{{ $product->formatted_size }} · {{ $product->platform ?: 'Software' }}</p>
<a class="btn btn-primary" href="{{ route('download.product', $product) }}" style="display:inline-flex;margin-top:18px">Download {{ $product->name }} ↓</a>
<div class="distribution-product-description">{!! \Illuminate\Support\Str::markdown($product->description ?: 'Download the latest available package.', ['html_input' => 'strip', 'allow_unsafe_links' => false]) !!}</div>
<a class="btn btn-primary" href="{{ route('download.product', $product) }}">Download ↓</a>
</article></div></main>
@endsection
+21
View File
@@ -332,6 +332,27 @@
@media (max-width: 640px) {
.download-page .distribution-catalogue > .download-shell { width: calc(100% - 28px); }
}
/* Compact product catalogue */
.download-page .distribution-catalogue > .download-shell { width:min(100% - 40px,1200px);max-width:1200px;margin-inline:auto; }
.download-page .distribution-product-grid { display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,330px),1fr));gap:20px;align-items:stretch; }
.download-page .distribution-product-card { display:flex;flex-direction:column;width:auto;min-width:0;padding:26px;border-radius:18px; }
.download-page .distribution-product-card h3 {font-size:clamp(25px,2vw,30px);font-weight:800;letter-spacing:-.035em;line-height:1.15;margin:20px 0 12px;color:#101828;}
.download-page .distribution-product-description {font-size:15px;line-height:1.6;margin:0 0 20px;color:#475467;}
.download-page .distribution-product-footer {margin-top:auto;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;}
.download-page .distribution-product-links {display:flex;align-items:center;gap:12px;flex-wrap:wrap;}
.download-page .distribution-product-links .product-details-link {color:#175cd3;font-weight:700;text-decoration:none;}
.download-page .distribution-product-links .product-details-link:hover {text-decoration:underline;}
.download-page .product-detail-panel {background:white;border:1px solid #e3e8ef;border-radius:18px;padding:clamp(24px,4vw,52px);margin:40px auto 70px;max-width:960px;}
.download-page .product-detail-panel h1 {font-size:clamp(32px,4vw,48px);font-weight:800;letter-spacing:-.04em;line-height:1.1;margin:14px 0;}
.download-page .product-detail-panel .distribution-product-description {margin-top:30px;font-size:16px;}
.download-page .product-detail-panel .distribution-product-description h2 {font-size:24px;margin:30px 0 12px;}
.download-page .product-detail-panel .distribution-product-description h3 {font-size:19px;margin:24px 0 10px;}
.download-page .product-detail-panel .distribution-product-description ul {list-style:disc;padding-left:25px;}
.download-page .product-detail-panel .distribution-product-description ol {list-style:decimal;padding-left:25px;}
.download-page .product-detail-panel .distribution-product-description li {margin:6px 0;}
@media(max-width:700px){.download-page .distribution-product-grid{grid-template-columns:1fr;}}
</style>
@stack('head')
</head>
+1
View File
@@ -26,6 +26,7 @@ Route::get('/', fn () => view('portal.welcome', ['latestRelease' => \App\Models\
Route::view('/features', 'features')->name('features');
Route::get('/release', [ReleaseController::class, 'index'])->name('release');
Route::get('/download', [ReleaseController::class, 'latest'])->name('download.latest');
Route::get('/downloads/products/{product}/details', [ReleaseController::class, 'productDetails'])->name('download.product.details');
Route::get('/downloads/products/{product}', [ReleaseController::class, 'productDownload'])->name('download.product');
Route::get('/downloads/{release}', [ReleaseController::class, 'download'])->name('download.release');
Route::get('/updates/appcast.xml', [ReleaseController::class, 'appcast'])->name('updates.appcast');