feat: Phase 2 - page CRUD, subscriber management, user management

This commit is contained in:
2026-04-03 21:15:40 +02:00
parent 78e1be3e3b
commit cf026f46b0
33 changed files with 1135 additions and 82 deletions

View File

@@ -5,43 +5,128 @@ declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\StorePreregistrationPageRequest;
use App\Http\Requests\Admin\UpdatePreregistrationPageRequest;
use App\Models\PreregistrationPage;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\View\View;
class PageController extends Controller
{
public function index(): \Illuminate\View\View
public function __construct()
{
return view('admin.pages.index');
$this->authorizeResource(PreregistrationPage::class, 'page');
}
public function create(): \Illuminate\View\View
public function index(Request $request): View
{
$query = PreregistrationPage::query()
->withCount('subscribers')
->orderByDesc('start_date');
if (! $request->user()?->isSuperadmin()) {
$query->where('user_id', $request->user()->id);
} else {
$query->with('user');
}
$pages = $query->get();
return view('admin.pages.index', compact('pages'));
}
public function create(): View
{
return view('admin.pages.create');
}
public function store(Request $request): \Illuminate\Http\RedirectResponse
public function store(StorePreregistrationPageRequest $request): RedirectResponse
{
return redirect()->route('admin.pages.index');
$validated = $request->validated();
$background = $request->file('background_image');
$logo = $request->file('logo_image');
unset($validated['background_image'], $validated['logo_image']);
$validated['slug'] = (string) Str::uuid();
$validated['user_id'] = $request->user()->id;
$page = DB::transaction(function () use ($validated, $background, $logo): PreregistrationPage {
$page = PreregistrationPage::create($validated);
$paths = [];
if ($background !== null) {
$paths['background_image'] = $background->store("preregister/pages/{$page->id}", 'public');
}
if ($logo !== null) {
$paths['logo_image'] = $logo->store("preregister/pages/{$page->id}", 'public');
}
if ($paths !== []) {
$page->update($paths);
}
return $page->fresh();
});
return redirect()
->route('admin.pages.index')
->with('status', __('Page created. Public URL: :url', ['url' => url('/r/'.$page->slug)]));
}
public function show(PreregistrationPage $page): \Illuminate\View\View
public function show(PreregistrationPage $page): RedirectResponse
{
return view('admin.pages.show', compact('page'));
return redirect()->route('admin.pages.edit', $page);
}
public function edit(PreregistrationPage $page): \Illuminate\View\View
public function edit(PreregistrationPage $page): View
{
return view('admin.pages.edit', compact('page'));
}
public function update(Request $request, PreregistrationPage $page): \Illuminate\Http\RedirectResponse
public function update(UpdatePreregistrationPageRequest $request, PreregistrationPage $page): RedirectResponse
{
return redirect()->route('admin.pages.index');
$validated = $request->validated();
$background = $request->file('background_image');
$logo = $request->file('logo_image');
unset($validated['background_image'], $validated['logo_image']);
DB::transaction(function () use ($validated, $background, $logo, $page): void {
if ($background !== null) {
if ($page->background_image !== null) {
Storage::disk('public')->delete($page->background_image);
}
$validated['background_image'] = $background->store("preregister/pages/{$page->id}", 'public');
}
if ($logo !== null) {
if ($page->logo_image !== null) {
Storage::disk('public')->delete($page->logo_image);
}
$validated['logo_image'] = $logo->store("preregister/pages/{$page->id}", 'public');
}
$page->update($validated);
});
return redirect()
->route('admin.pages.index')
->with('status', __('Page updated.'));
}
public function destroy(PreregistrationPage $page): \Illuminate\Http\RedirectResponse
public function destroy(PreregistrationPage $page): RedirectResponse
{
return redirect()->route('admin.pages.index');
DB::transaction(function () use ($page): void {
if ($page->background_image !== null) {
Storage::disk('public')->delete($page->background_image);
}
if ($page->logo_image !== null) {
Storage::disk('public')->delete($page->logo_image);
}
$page->delete();
});
return redirect()
->route('admin.pages.index')
->with('status', __('Page deleted.'));
}
}

View File

@@ -5,34 +5,58 @@ declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\IndexSubscriberRequest;
use App\Models\PreregistrationPage;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;
class SubscriberController extends Controller
{
public function index(PreregistrationPage $page): \Illuminate\View\View
public function index(IndexSubscriberRequest $request, PreregistrationPage $page): View
{
return view('admin.subscribers.index', compact('page'));
$search = $request->validated('search');
$subscribers = $page->subscribers()
->search(is_string($search) ? $search : null)
->orderByDesc('created_at')
->paginate(25)
->withQueryString();
return view('admin.subscribers.index', compact('page', 'subscribers'));
}
public function export(PreregistrationPage $page): \Symfony\Component\HttpFoundation\StreamedResponse
public function export(IndexSubscriberRequest $request, PreregistrationPage $page): StreamedResponse
{
$this->authorize('view', $page);
$search = $request->validated('search');
$subscribers = $page->subscribers()
->search(is_string($search) ? $search : null)
->orderBy('created_at')
->get();
$subscribers = $page->subscribers()->orderBy('created_at')->get();
$phoneEnabled = $page->phone_enabled;
return response()->streamDownload(function () use ($subscribers, $page) {
return response()->streamDownload(function () use ($subscribers, $phoneEnabled): void {
$handle = fopen('php://output', 'w');
fputcsv($handle, ['First Name', 'Last Name', 'Email', 'Phone', 'Registered At']);
foreach ($subscribers as $sub) {
fputcsv($handle, [
$sub->first_name,
$sub->last_name,
$sub->email,
$sub->phone,
$sub->created_at->toDateTimeString(),
]);
$headers = ['First Name', 'Last Name', 'Email'];
if ($phoneEnabled) {
$headers[] = 'Phone';
}
$headers = array_merge($headers, ['Registered At', 'Synced to Mailwizz', 'Synced At']);
fputcsv($handle, $headers);
foreach ($subscribers as $sub) {
$row = [$sub->first_name, $sub->last_name, $sub->email];
if ($phoneEnabled) {
$row[] = $sub->phone ?? '';
}
$row[] = $sub->created_at?->toDateTimeString() ?? '';
$row[] = $sub->synced_to_mailwizz ? 'Yes' : 'No';
$row[] = $sub->synced_at?->toDateTimeString() ?? '';
fputcsv($handle, $row);
}
fclose($handle);
}, "subscribers-{$page->slug}.csv");
}, 'subscribers-'.$page->slug.'.csv', [
'Content-Type' => 'text/csv; charset=UTF-8',
]);
}
}

View File

@@ -5,38 +5,71 @@ declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\StoreUserRequest;
use App\Http\Requests\Admin\UpdateUserRequest;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Hash;
use Illuminate\View\View;
class UserController extends Controller
{
public function index(): \Illuminate\View\View
public function __construct()
{
return view('admin.users.index');
$this->authorizeResource(User::class, 'user', [
'except' => ['show'],
]);
}
public function create(): \Illuminate\View\View
public function index(): View
{
$users = User::query()->orderBy('name')->paginate(25);
return view('admin.users.index', compact('users'));
}
public function create(): View
{
return view('admin.users.create');
}
public function store(Request $request): \Illuminate\Http\RedirectResponse
public function store(StoreUserRequest $request): RedirectResponse
{
return redirect()->route('admin.users.index');
$data = $request->validated();
$data['password'] = Hash::make($data['password']);
User::query()->create($data);
return redirect()
->route('admin.users.index')
->with('status', __('User created.'));
}
public function edit(User $user): \Illuminate\View\View
public function edit(User $user): View
{
return view('admin.users.edit', compact('user'));
}
public function update(Request $request, User $user): \Illuminate\Http\RedirectResponse
public function update(UpdateUserRequest $request, User $user): RedirectResponse
{
return redirect()->route('admin.users.index');
$data = $request->validated();
if ($request->filled('password')) {
$data['password'] = Hash::make($data['password']);
} else {
unset($data['password']);
}
$user->update($data);
return redirect()
->route('admin.users.index')
->with('status', __('User updated.'));
}
public function destroy(User $user): \Illuminate\Http\RedirectResponse
public function destroy(User $user): RedirectResponse
{
return redirect()->route('admin.users.index');
$user->delete();
return redirect()
->route('admin.users.index')
->with('status', __('User deleted.'));
}
}