Files
ladill-give/app/Http/Controllers/Give/GivingPageController.php
T
isaaccladandCursor 4145c5f7d5
Deploy Ladill Give / deploy (push) Successful in 1m5s
Add Merchant-style hero to Giving Pages index.
Surface wallet balance, page count, and active stats in a gradient hero with balance-gated create CTA.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 20:39:40 +00:00

219 lines
8.1 KiB
PHP

<?php
namespace App\Http\Controllers\Give;
use App\Http\Controllers\Controller;
use App\Models\QrCode;
use App\Models\QrWallet;
use App\Services\Qr\QrCodeManagerService;
use App\Services\Qr\QrImageGeneratorService;
use App\Services\Qr\QrPdfExporter;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\View\View;
use RuntimeException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
class GivingPageController extends Controller
{
public function __construct(
private QrCodeManagerService $manager,
private QrImageGeneratorService $imageGenerator,
private QrPdfExporter $pdfExporter,
) {}
public function index(Request $request): View
{
$account = ladill_account();
$qrCodes = $account->qrCodes()
->where('type', QrCode::TYPE_CHURCH)
->latest()
->get();
$previewDataUris = $qrCodes->mapWithKeys(function (QrCode $qr) {
return [$qr->id => $this->imageGenerator->previewDataUri($qr)];
});
$wallet = $this->manager->walletFor($account);
return view('give.giving-pages.index', [
'qrCodes' => $qrCodes,
'previewDataUris' => $previewDataUris,
'wallet' => $wallet,
'activeCount' => $qrCodes->where('is_active', true)->count(),
'pricePerQr' => QrWallet::pricePerQr(),
'topupUrl' => 'https://'.config('app.account_domain').'/wallet',
]);
}
public function create(): View
{
return view('give.giving-pages.create');
}
public function store(Request $request): RedirectResponse
{
$account = ladill_account();
$request->validate([
'label' => ['required', 'string', 'max:120'],
'name' => ['required', 'string', 'max:120'],
'org_type' => ['required', 'in:church,school,mosque,ngo,club'],
'denomination' => ['nullable', 'string', 'max:120'],
'description' => ['nullable', 'string', 'max:2000'],
'phone' => ['nullable', 'string', 'max:30'],
'email' => ['nullable', 'email', 'max:200'],
'website' => ['nullable', 'url', 'max:2048'],
'address' => ['nullable', 'string', 'max:500'],
'service_times' => ['nullable', 'string', 'max:200'],
'brand_color' => ['nullable', 'string', 'max:20'],
'collection_types' => ['nullable', 'array', 'min:1'],
'collection_types.*' => ['string', 'max:80'],
'church_logo' => ['nullable', 'image', 'mimes:jpeg,jpg,png,gif,webp', 'max:4096'],
'church_cover' => ['nullable', 'image', 'mimes:jpeg,jpg,png,gif,webp', 'max:8192'],
]);
$data = array_merge($request->all(), [
'type' => QrCode::TYPE_CHURCH,
'currency' => 'GHS',
'accepts_payment' => true,
'church_logo' => $request->file('church_logo'),
'church_cover' => $request->file('church_cover'),
]);
try {
$qrCode = $this->manager->create($account, $data);
} catch (RuntimeException $e) {
return back()->withInput()->with('error', $e->getMessage());
}
return redirect()->route('give.giving-pages.show', $qrCode)->with('success', 'Giving page created.');
}
public function show(QrCode $givingPage): View
{
$this->authorizeGivingPage($givingPage);
return view('give.giving-pages.show', [
'qrCode' => $givingPage->fresh(),
'previewDataUri' => $this->imageGenerator->previewDataUri($givingPage),
]);
}
public function update(Request $request, QrCode $givingPage): RedirectResponse
{
$this->authorizeGivingPage($givingPage);
$request->validate([
'label' => ['sometimes', 'string', 'max:120'],
'name' => ['sometimes', 'string', 'max:120'],
'org_type' => ['sometimes', 'in:church,school,mosque,ngo,club'],
'denomination' => ['nullable', 'string', 'max:120'],
'description' => ['nullable', 'string', 'max:2000'],
'phone' => ['nullable', 'string', 'max:30'],
'email' => ['nullable', 'email', 'max:200'],
'website' => ['nullable', 'url', 'max:2048'],
'address' => ['nullable', 'string', 'max:500'],
'service_times' => ['nullable', 'string', 'max:200'],
'brand_color' => ['nullable', 'string', 'max:20'],
'collection_types' => ['nullable', 'array', 'min:1'],
'collection_types.*' => ['string', 'max:80'],
'is_active' => ['sometimes', 'boolean'],
'church_logo' => ['nullable', 'image', 'mimes:jpeg,jpg,png,gif,webp', 'max:4096'],
'church_cover' => ['nullable', 'image', 'mimes:jpeg,jpg,png,gif,webp', 'max:8192'],
]);
$data = array_merge($request->all(), [
'accepts_payment' => true,
'is_active' => $request->boolean('is_active', $givingPage->is_active),
'church_logo' => $request->file('church_logo'),
'church_cover' => $request->file('church_cover'),
]);
try {
$this->manager->update($givingPage, $data);
} catch (RuntimeException $e) {
return back()->withInput()->with('error', $e->getMessage());
}
return back()->with('success', 'Giving page updated.');
}
public function destroy(QrCode $givingPage): RedirectResponse
{
$this->authorizeGivingPage($givingPage);
$this->manager->delete($givingPage);
return redirect()
->route('give.giving-pages.index')
->with('success', 'Giving page deleted.');
}
public function preview(QrCode $givingPage): Response
{
$this->authorizeGivingPage($givingPage);
$qrCode = $this->imageGenerator->ensureValidImages($givingPage);
$bytes = $this->imageGenerator->normalizeStoredPng($qrCode->png_path);
if ($bytes === null) {
$bytes = $this->imageGenerator->renderPng($qrCode->encodedPayload(), $qrCode->style());
$this->imageGenerator->generateAndStore($qrCode);
}
return response($bytes, 200, [
'Content-Type' => 'image/png',
'Cache-Control' => 'private, max-age=3600',
]);
}
public function download(QrCode $givingPage, string $format): StreamedResponse
{
$this->authorizeGivingPage($givingPage);
$qrCode = $this->imageGenerator->ensureValidImages($givingPage);
$path = $format === 'svg' ? $qrCode->svg_path : $qrCode->png_path;
$filename = Str::slug($qrCode->label).'-qr.'.($format === 'svg' ? 'svg' : 'png');
if ($format === 'png') {
$bytes = $this->imageGenerator->normalizeStoredPng($path);
if ($bytes === null) {
$bytes = $this->imageGenerator->renderPng($qrCode->encodedPayload(), $qrCode->style());
$this->imageGenerator->generateAndStore($qrCode);
}
return response()->streamDownload(fn () => print($bytes), $filename, [
'Content-Type' => 'image/png',
]);
}
if ($format === 'pdf') {
$bytes = $this->imageGenerator->normalizeStoredPng($qrCode->png_path);
if ($bytes === null) {
$bytes = $this->imageGenerator->renderPng($qrCode->encodedPayload(), $qrCode->style());
$this->imageGenerator->generateAndStore($qrCode);
}
$pdf = $this->pdfExporter->fromPng($bytes, $qrCode->label);
return response()->streamDownload(fn () => print($pdf), Str::slug($qrCode->label).'-qr.pdf', [
'Content-Type' => 'application/pdf',
]);
}
abort_unless($path && Storage::disk('qr')->exists($path), 404);
return Storage::disk('qr')->download($path, $filename);
}
private function authorizeGivingPage(QrCode $givingPage): void
{
abort_unless($givingPage->type === QrCode::TYPE_CHURCH, 404);
abort_unless($givingPage->user_id === ladill_account()->id, 403);
}
}