Add Portfolio and Bookshop QR types with tailored landing pages.
Deploy Ladill QR Plus / deploy (push) Successful in 58s

Portfolio showcases freelancer work projects; Bookshop lists author titles with multi-platform purchase links. Includes create/edit forms, public assets, and tests.
This commit is contained in:
isaacclad
2026-07-16 20:56:52 +00:00
parent 95d73d1367
commit d83a314bee
13 changed files with 989 additions and 1 deletions
+96
View File
@@ -103,6 +103,38 @@ class QrCodeManagerService
}
}
if ($type === QrCode::TYPE_PORTFOLIO) {
if (($data['portfolio_avatar'] ?? null) instanceof UploadedFile) {
$content['avatar_path'] = $this->storeMenuBrandImage($user, $data['portfolio_avatar'], 'portfolio-avatars');
}
if (($data['portfolio_cover'] ?? null) instanceof UploadedFile) {
$content['cover_path'] = $this->storeMenuBrandImage($user, $data['portfolio_cover'], 'portfolio-covers');
}
$content['projects'] = $this->injectListImages(
$user,
$content['projects'] ?? [],
$data['project_images'] ?? [],
'portfolio-projects',
'image_path',
);
}
if ($type === QrCode::TYPE_BOOKSHOP) {
if (($data['bookshop_avatar'] ?? null) instanceof UploadedFile) {
$content['avatar_path'] = $this->storeMenuBrandImage($user, $data['bookshop_avatar'], 'bookshop-avatars');
}
if (($data['bookshop_cover'] ?? null) instanceof UploadedFile) {
$content['cover_path'] = $this->storeMenuBrandImage($user, $data['bookshop_cover'], 'bookshop-covers');
}
$content['books'] = $this->injectListImages(
$user,
$content['books'] ?? [],
$data['book_covers'] ?? [],
'bookshop-books',
'cover_path',
);
}
if ($type === QrCode::TYPE_CHURCH) {
if (($data['church_logo'] ?? null) instanceof UploadedFile) {
$content['logo_path'] = $this->storeMenuBrandImage($user, $data['church_logo'], 'church-logos');
@@ -245,6 +277,38 @@ class QrCodeManagerService
}
}
if ($qrCode->type === QrCode::TYPE_PORTFOLIO) {
if (($data['portfolio_avatar'] ?? null) instanceof UploadedFile) {
$content['avatar_path'] = $this->storeMenuBrandImage($qrCode->user, $data['portfolio_avatar'], 'portfolio-avatars');
}
if (($data['portfolio_cover'] ?? null) instanceof UploadedFile) {
$content['cover_path'] = $this->storeMenuBrandImage($qrCode->user, $data['portfolio_cover'], 'portfolio-covers');
}
$content['projects'] = $this->injectListImages(
$qrCode->user,
$content['projects'] ?? [],
$data['project_images'] ?? [],
'portfolio-projects',
'image_path',
);
}
if ($qrCode->type === QrCode::TYPE_BOOKSHOP) {
if (($data['bookshop_avatar'] ?? null) instanceof UploadedFile) {
$content['avatar_path'] = $this->storeMenuBrandImage($qrCode->user, $data['bookshop_avatar'], 'bookshop-avatars');
}
if (($data['bookshop_cover'] ?? null) instanceof UploadedFile) {
$content['cover_path'] = $this->storeMenuBrandImage($qrCode->user, $data['bookshop_cover'], 'bookshop-covers');
}
$content['books'] = $this->injectListImages(
$qrCode->user,
$content['books'] ?? [],
$data['book_covers'] ?? [],
'bookshop-books',
'cover_path',
);
}
if ($qrCode->type === QrCode::TYPE_CHURCH) {
if (($data['church_logo'] ?? null) instanceof UploadedFile) {
$content['logo_path'] = $this->storeMenuBrandImage($qrCode->user, $data['church_logo'], 'church-logos');
@@ -354,6 +418,8 @@ class QrCodeManagerService
QrCode::TYPE_SHOP => ['shop_title', 'sections', 'currency', 'accepts_payment', 'brand_color', 'shipping_type', 'shipping_fee', 'free_shipping_above'],
QrCode::TYPE_APP => ['app_name', 'ios_url', 'android_url', 'web_url', 'app_icon'],
QrCode::TYPE_BOOK => ['book_title', 'author', 'description', 'price_ghs'],
QrCode::TYPE_PORTFOLIO => ['name', 'role', 'bio', 'email', 'phone', 'website', 'brand_color', 'projects', 'social'],
QrCode::TYPE_BOOKSHOP => ['author_name', 'tagline', 'bio', 'email', 'website', 'brand_color', 'books', 'social'],
QrCode::TYPE_WIFI => ['ssid', 'password', 'encryption', 'hidden'],
default => [],
};
@@ -480,6 +546,36 @@ class QrCodeManagerService
return $path;
}
/**
* Inject uploaded list images (project images / book covers) into a flat list.
*
* @param array<int, mixed> $items
* @param array<int, mixed> $uploads
* @return array<int, mixed>
*/
private function injectListImages(User $user, array $items, array $uploads, string $subdir, string $pathKey): array
{
if ($uploads === []) {
return $items;
}
foreach ($uploads as $index => $file) {
if (! ($file instanceof UploadedFile) || ! isset($items[$index])) {
continue;
}
$mime = $file->getMimeType() ?: '';
if (! str_starts_with($mime, 'image/')) {
continue;
}
$ext = $file->getClientOriginalExtension() ?: 'jpg';
$path = $user->id.'/'.$subdir.'/'.Str::uuid()->toString().'.'.$ext;
$file->storeAs('', $path, 'qr');
$items[$index][$pathKey] = $path;
}
return $items;
}
private function storeBookCover(User $user, UploadedFile $file): string
{
$mime = $file->getMimeType() ?: '';