Fix 'My Membership' 404 by adding missing profile flow

- Added a 'Create Member Profile' page for existing users who don't have a member record.
- Updated MemberDashboardController to redirect to profile creation instead of aborting 404.
- Added 'member.profile.create' and 'member.profile.store' routes.
This commit is contained in:
2025-11-28 00:25:04 +08:00
parent c7a1f9130e
commit 6890cf085d
3 changed files with 183 additions and 2 deletions

View File

@@ -2,7 +2,10 @@
namespace App\Http\Controllers;
use App\Models\Member;
use App\Support\AuditLogger;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class MemberDashboardController extends Controller
{
@@ -12,7 +15,7 @@ class MemberDashboardController extends Controller
$member = $user->member;
if (! $member) {
abort(404);
return redirect()->route('member.profile.create');
}
$member->load([
@@ -31,5 +34,58 @@ class MemberDashboardController extends Controller
'pendingPayment' => $pendingPayment,
]);
}
public function createProfile()
{
$user = Auth::user();
if ($user->member) {
return redirect()->route('member.dashboard');
}
return view('member.create-profile');
}
public function storeProfile(Request $request)
{
$user = Auth::user();
if ($user->member) {
return redirect()->route('member.dashboard');
}
$validated = $request->validate([
'full_name' => ['required', 'string', 'max:255'],
'phone' => ['nullable', 'string', 'max:20'],
'national_id' => ['nullable', 'string', 'max:20'],
'address_line_1' => ['nullable', 'string', 'max:255'],
'address_line_2' => ['nullable', 'string', 'max:255'],
'city' => ['nullable', 'string', 'max:100'],
'postal_code' => ['nullable', 'string', 'max:10'],
'emergency_contact_name' => ['nullable', 'string', 'max:255'],
'emergency_contact_phone' => ['nullable', 'string', 'max:20'],
'terms_accepted' => ['required', 'accepted'],
]);
$member = Member::create([
'user_id' => $user->id,
'full_name' => $validated['full_name'],
'email' => $user->email,
'phone' => $validated['phone'] ?? null,
'national_id' => $validated['national_id'] ?? null,
'address_line_1' => $validated['address_line_1'] ?? null,
'address_line_2' => $validated['address_line_2'] ?? null,
'city' => $validated['city'] ?? null,
'postal_code' => $validated['postal_code'] ?? null,
'emergency_contact_name' => $validated['emergency_contact_name'] ?? null,
'emergency_contact_phone' => $validated['emergency_contact_phone'] ?? null,
'membership_status' => Member::STATUS_PENDING,
'membership_type' => Member::TYPE_REGULAR,
]);
AuditLogger::log('member.created_profile', $member, [
'user_id' => $user->id,
'name' => $member->full_name,
]);
return redirect()->route('member.dashboard')
->with('status', __('Profile completed! Please submit your membership payment.'));
}
}

View File

@@ -0,0 +1,121 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Complete Your Membership Profile') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg dark:bg-gray-800">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="mb-6">
{{ __('To access the member area, please provide your membership details. This information is required for our records.') }}
</div>
<form method="POST" action="{{ route('member.profile.store') }}" class="space-y-6">
@csrf
{{-- Basic Information --}}
<div class="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">{{ __('Basic Information') }}</h3>
<!-- Full Name -->
<div>
<x-input-label for="full_name" :value="__('Full Name')" />
<x-text-input id="full_name" class="block mt-1 w-full" type="text" name="full_name" :value="old('full_name', Auth::user()->name)" required autofocus />
<x-input-error :messages="$errors->get('full_name')" class="mt-2" />
</div>
<!-- Phone -->
<div class="mt-4">
<x-input-label for="phone" :value="__('Phone')" />
<x-text-input id="phone" class="block mt-1 w-full" type="text" name="phone" :value="old('phone')" />
<x-input-error :messages="$errors->get('phone')" class="mt-2" />
</div>
<!-- National ID (Optional) -->
<div class="mt-4">
<x-input-label for="national_id" :value="__('National ID (Optional)')" />
<x-text-input id="national_id" class="block mt-1 w-full" type="text" name="national_id" :value="old('national_id')" maxlength="20" />
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ __('Your national ID will be encrypted for security.') }}</p>
<x-input-error :messages="$errors->get('national_id')" class="mt-2" />
</div>
</div>
{{-- Address Information --}}
<div class="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">{{ __('Address') }}</h3>
<!-- Address Line 1 -->
<div>
<x-input-label for="address_line_1" :value="__('Address Line 1')" />
<x-text-input id="address_line_1" class="block mt-1 w-full" type="text" name="address_line_1" :value="old('address_line_1')" />
<x-input-error :messages="$errors->get('address_line_1')" class="mt-2" />
</div>
<!-- Address Line 2 -->
<div class="mt-4">
<x-input-label for="address_line_2" :value="__('Address Line 2')" />
<x-text-input id="address_line_2" class="block mt-1 w-full" type="text" name="address_line_2" :value="old('address_line_2')" />
<x-input-error :messages="$errors->get('address_line_2')" class="mt-2" />
</div>
<div class="grid grid-cols-2 gap-4 mt-4">
<!-- City -->
<div>
<x-input-label for="city" :value="__('City')" />
<x-text-input id="city" class="block mt-1 w-full" type="text" name="city" :value="old('city')" />
<x-input-error :messages="$errors->get('city')" class="mt-2" />
</div>
<!-- Postal Code -->
<div>
<x-input-label for="postal_code" :value="__('Postal Code')" />
<x-text-input id="postal_code" class="block mt-1 w-full" type="text" name="postal_code" :value="old('postal_code')" />
<x-input-error :messages="$errors->get('postal_code')" class="mt-2" />
</div>
</div>
</div>
{{-- Emergency Contact --}}
<div class="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">{{ __('Emergency Contact') }}</h3>
<!-- Emergency Contact Name -->
<div>
<x-input-label for="emergency_contact_name" :value="__('Emergency Contact Name')" />
<x-text-input id="emergency_contact_name" class="block mt-1 w-full" type="text" name="emergency_contact_name" :value="old('emergency_contact_name')" />
<x-input-error :messages="$errors->get('emergency_contact_name')" class="mt-2" />
</div>
<!-- Emergency Contact Phone -->
<div class="mt-4">
<x-input-label for="emergency_contact_phone" :value="__('Emergency Contact Phone')" />
<x-text-input id="emergency_contact_phone" class="block mt-1 w-full" type="text" name="emergency_contact_phone" :value="old('emergency_contact_phone')" />
<x-input-error :messages="$errors->get('emergency_contact_phone')" class="mt-2" />
</div>
</div>
{{-- Terms and Conditions --}}
<div class="mt-4">
<label class="inline-flex items-center">
<input type="checkbox" name="terms_accepted" value="1" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:focus:ring-indigo-600 dark:focus:ring-offset-gray-800" {{ old('terms_accepted') ? 'checked' : '' }} required>
<span class="ml-2 text-sm text-gray-600 dark:text-gray-400">
{{ __('I accept the terms and conditions and agree to submit payment for membership activation.') }}
</span>
</label>
<x-input-error :messages="$errors->get('terms_accepted')" class="mt-2" />
</div>
<div class="flex items-center justify-end mt-6">
<x-primary-button>
{{ __('Complete Profile') }}
</x-primary-button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -82,6 +82,10 @@ Route::middleware('auth')->group(function () {
// Member Payment Submission Routes
Route::get('/member/submit-payment', [MemberPaymentController::class, 'create'])->name('member.payments.create');
Route::post('/member/payments', [MemberPaymentController::class, 'store'])->name('member.payments.store');
Route::get('/create-member-profile', [MemberDashboardController::class, 'createProfile'])->name('member.profile.create');
Route::post('/create-member-profile', [MemberDashboardController::class, 'storeProfile'])->name('member.profile.store');
Route::get('/my-membership', [MemberDashboardController::class, 'show'])
->name('member.dashboard');