api update

This commit is contained in:
Abhishek Mali
2025-12-02 18:07:15 +05:30
parent 3845972c5c
commit 922539844d
12 changed files with 654 additions and 662 deletions

View File

@@ -65,4 +65,52 @@ class UserRequestController extends Controller
return redirect()->back()->with('info', 'Request rejected successfully.');
}
public function profileUpdateRequests()
{
$requests = \App\Models\UpdateRequest::where('status', 'pending')
->orderBy('id', 'desc')
->get();
return view('admin.profile_update_requests', compact('requests'));
}
public function approveProfileUpdate($id)
{
$req = \App\Models\UpdateRequest::findOrFail($id);
$user = \App\Models\User::findOrFail($req->user_id);
// FIX: Ensure data is array
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
foreach ($newData as $key => $value) {
if ($value !== null && $value !== "") {
if (in_array($key, ['customer_name','company_name','designation','email','mobile_no','address','pincode'])) {
$user->$key = $value;
}
}
}
$user->save();
$req->status = 'approved';
$req->admin_note = 'Approved by admin on ' . now();
$req->save();
return back()->with('success', 'Profile updated successfully.');
}
public function rejectProfileUpdate($id)
{
$req = \App\Models\UpdateRequest::findOrFail($id);
$req->status = 'rejected';
$req->admin_note = 'Rejected by admin on ' . now();
$req->save();
return back()->with('info', 'Profile update request rejected.');
}
}

View File

@@ -12,19 +12,68 @@ class UserAuthController extends Controller
public function refreshToken()
{
\Log::info('🔄 refreshToken() called');
try {
$newToken = JWTAuth::refresh(JWTAuth::getToken());
// Get current token
$currentToken = JWTAuth::getToken();
if (!$currentToken) {
\Log::warning('⚠ No token provided in refreshToken()');
return response()->json([
'success' => false,
'message' => 'Token not provided',
], 401);
}
\Log::info('📥 Current Token:', ['token' => (string) $currentToken]);
// Try refreshing token
$newToken = JWTAuth::refresh($currentToken);
\Log::info('✅ Token refreshed successfully', ['new_token' => $newToken]);
return response()->json([
'success' => true,
'token' => $newToken,
]);
} catch (\Exception $e) {
} catch (\Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
\Log::error('❌ TokenExpiredException in refreshToken()', [
'message' => $e->getMessage(),
]);
return response()->json([
'success' => false,
'message' => 'Token expired, cannot refresh.',
], 401);
} catch (\Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
\Log::error('❌ TokenInvalidException in refreshToken()', [
'message' => $e->getMessage(),
]);
return response()->json([
'success' => false,
'message' => 'Invalid token.',
], 401);
} catch (\Tymon\JWTAuth\Exceptions\JWTException $e) {
\Log::error('❌ JWTException in refreshToken()', [
'message' => $e->getMessage(),
]);
return response()->json([
'success' => false,
'message' => 'Could not refresh token.',
], 401);
} catch (\Exception $e) {
\Log::error('❌ General Exception in refreshToken()', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return response()->json([
'success' => false,
'message' => 'Unexpected error while refreshing token.',
], 500);
}
}

View File

@@ -4,14 +4,17 @@ namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\UpdateRequest;
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;
class UserProfileController extends Controller
{
/**
* Get user profile
*/
public function profile()
{
// Get logged-in user using JWT
try {
try {
$user = JWTAuth::parseToken()->authenticate();
} catch (\Exception $e) {
return response()->json([
@@ -20,7 +23,6 @@ class UserProfileController extends Controller
], 401);
}
if (! $user) {
return response()->json([
'success' => false,
@@ -28,7 +30,6 @@ class UserProfileController extends Controller
], 401);
}
// Format response
return response()->json([
'success' => true,
'data' => [
@@ -49,7 +50,53 @@ class UserProfileController extends Controller
]);
}
public function updateProfile(Request $request)
/**
* Update profile IMAGE only (no admin approval)
*/
public function updateProfileImage(Request $request)
{
$user = JWTAuth::parseToken()->authenticate();
if (! $user) {
return response()->json([
'success' => false,
'message' => 'Unauthorized'
], 401);
}
$request->validate([
'profile_image' => 'required|image|mimes:jpg,jpeg,png|max:2048'
]);
// DELETE OLD IMAGE
if ($user->profile_image && file_exists(public_path($user->profile_image))) {
@unlink(public_path($user->profile_image));
}
// SAVE NEW IMAGE
$file = $request->file('profile_image');
$filename = 'profile_' . time() . '.' . $file->getClientOriginalExtension();
$folder = 'profile_upload/';
$file->move(public_path($folder), $filename);
$user->profile_image = $folder . $filename;
$user->save();
return response()->json([
'success' => true,
'message' => 'Profile image updated successfully',
'profile_image' => url($user->profile_image),
]);
}
/**
* Submit profile update request (requires admin approval)
*/
public function updateProfileRequest(Request $request)
{
$user = JWTAuth::parseToken()->authenticate();
@@ -60,53 +107,28 @@ class UserProfileController extends Controller
], 401);
}
// Validate ONLY profile image
// Validate input
$request->validate([
'profile_image' => 'required|image|mimes:jpg,jpeg,png|max:2048'
'customer_name' => 'nullable|string|max:255',
'company_name' => 'nullable|string|max:255',
'designation' => 'nullable|string|max:255',
'email' => 'nullable|email',
'mobile_no' => 'nullable|string|max:15',
'address' => 'nullable|string',
'pincode' => 'nullable|string|max:10'
]);
// If new image uploaded
if ($request->hasFile('profile_image')) {
// DELETE OLD IMAGE
if ($user->profile_image && file_exists(public_path($user->profile_image))) {
@unlink(public_path($user->profile_image));
}
// NEW FILE
$file = $request->file('profile_image');
$filename = 'profile_' . time() . '.' . $file->getClientOriginalExtension();
// Correct folder name (from your message)
$folder = 'profile_upload/';
$fullPath = $folder . $filename;
// Move file
$file->move(public_path($folder), $filename);
// Save in DB (same pattern you said)
$user->profile_image = $fullPath;
}
$user->save();
// SAVE AS ARRAY (NOT JSON STRING!)
$updateReq = \App\Models\UpdateRequest::create([
'user_id' => $user->id,
'data' => $request->all(), // <---- FIXED
'status' => 'pending',
]);
return response()->json([
'success' => true,
'message' => 'Profile image updated successfully',
'data' => [
'customer_id' => $user->customer_id,
'customer_name' => $user->customer_name,
'company_name' => $user->company_name,
'designation' => $user->designation,
'email' => $user->email,
'mobile' => $user->mobile_no,
'address' => $user->address,
'pincode' => $user->pincode,
'status' => $user->status,
'customer_type' => $user->customer_type,
'profile_image' => $user->profile_image ? url($user->profile_image) : null,
'date' => $user->date,
]
'message' => 'Profile update request submitted. Waiting for admin approval.',
'request_id' => $updateReq->id
]);
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UpdateRequest extends Model
{
use HasFactory;
protected $table = 'update_requests';
protected $fillable = [
'user_id',
'data',
'status',
'admin_note',
];
protected $casts = [
'data' => 'array', // converts JSON to array automatically
];
// Relationship: request belongs to a user
public function user()
{
return $this->belongsTo(User::class);
}
}

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUpdateRequestsTable extends Migration
{
public function up()
{
Schema::create('update_requests', function (Blueprint $table) {
$table->id();
// The user who is requesting profile update
$table->unsignedBigInteger('user_id');
// JSON data of the requested profile changes
$table->json('data')->nullable();
// pending / approved / rejected
$table->enum('status', ['pending', 'approved', 'rejected'])->default('pending');
// Optional message (admin notes)
$table->text('admin_note')->nullable();
$table->timestamps();
// Foreign key constraint
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
}
public function down()
{
Schema::dropIfExists('update_requests');
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -212,6 +212,13 @@
<i class="bi bi-bag"></i> Orders
</a>
<a href="{{ route('admin.requests') }}" class="{{ request()->routeIs('admin.requests') ? 'active' : '' }}"><i class="bi bi-envelope"></i> Requests</a>
<li>
<a href="{{ route('admin.profile.requests') }}">
<i class="bi bi-person-lines-fill"></i>
Profile Update Requests
</a>
</li>
<a href="{{ route('admin.staff') }}" class="{{ request()->routeIs('admin.staff') ? 'active' : '' }}"><i class="bi bi-person-badge"></i> Staff</a>
<a href="{{ route('admin.account') }}" class="{{ request()->routeIs('admin.account') ? 'active' : '' }}"><i class="bi bi-gear"></i> Account</a>
<a href="{{ route('admin.marklist.index') }}" class="{{ request()->routeIs('admin.marklist.index') ? 'active' : '' }}"><i class="bi bi-list-check"></i> Mark List</a>

View File

@@ -0,0 +1,111 @@
@extends('admin.layouts.app')
@section('page-title', 'Profile Update Requests')
@section('content')
<div class="container-fluid px-0">
@php
$perPage = 5;
$currentPage = request()->get('page', 1);
$currentPage = max(1, (int)$currentPage);
$total = $requests->count();
$totalPages = ceil($total / $perPage);
$currentItems = $requests->slice(($currentPage - 1) * $perPage, $perPage);
@endphp
<style>
.old-value { color: #6b7280; font-weight: 600; }
.new-value { color: #111827; font-weight: 700; }
.changed { background: #fef3c7; padding: 6px; border-radius: 6px; }
.box { padding: 10px 14px; border-radius: 8px; background: #f8fafc; margin-bottom: 10px; }
.diff-label { font-size: 13px; font-weight: 700; }
.actions { display: flex; gap: 10px; }
</style>
<h4 class="fw-bold my-3">Profile Update Requests ({{ $total }})</h4>
<div class="card mb-4 shadow-sm">
<div class="card-body pb-1">
<div class="table-responsive custom-table-wrapper">
<table class="table align-middle mb-0 custom-table">
<thead>
<tr>
<th>#</th>
<th>User</th>
<th>Requested Changes</th>
<th>Status</th>
<th>Requested At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($currentItems as $index => $req)
@php
$user = $req->user;
// FIX: Convert string to array
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
@endphp
<tr>
<td><strong>{{ ($currentPage - 1) * $perPage + $index + 1 }}</strong></td>
<td>
<strong>{{ $user->customer_name }}</strong><br>
<small>{{ $user->email }}</small><br>
<small>ID: {{ $user->customer_id }}</small>
</td>
<td>
@foreach($newData as $key => $newValue)
@php
$oldValue = $user->$key ?? '—';
$changed = $oldValue != $newValue;
@endphp
<div class="box {{ $changed ? 'changed' : '' }}">
<span class="diff-label">{{ ucfirst(str_replace('_',' ', $key)) }}:</span><br>
<span class="old-value">Old: {{ $oldValue }}</span><br>
<span class="new-value">New: {{ $newValue ?? '—' }}</span>
</div>
@endforeach
</td>
<td>
@if($req->status == 'pending')
<span class="badge badge-pending">Pending</span>
@elseif($req->status == 'approved')
<span class="badge badge-approved">Approved</span>
@else
<span class="badge badge-rejected">Rejected</span>
@endif
</td>
<td>{{ $req->created_at->format('d M Y, h:i A') }}</td>
<td class="actions">
@if($req->status == 'pending')
<a href="{{ route('admin.profile.approve', $req->id) }}" class="btn btn-success btn-sm">
<i class="bi bi-check-circle"></i> Approve
</a>
<a href="{{ route('admin.profile.reject', $req->id) }}" class="btn btn-danger btn-sm">
<i class="bi bi-x-circle"></i> Reject
</a>
@else
<span class="text-muted">Completed</span>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection

File diff suppressed because it is too large Load Diff

View File

@@ -37,6 +37,11 @@ Route::middleware(['auth:api'])->group(function () {
Route::get('/user/invoice/{invoice_id}/installments', [UserOrderController::class, 'invoiceInstallmentsById']);
// Profile
Route::get('/user/profile', [UserProfileController::class, 'profile']);
Route::post('/user/profile/update', [UserProfileController::class, 'updateProfile']);
Route::post('/user/profile-image', [UserProfileController::class, 'updateProfileImage']);
Route::post('/user/profile-update-request', [UserProfileController::class, 'updateProfileRequest']);
// Route::post('/user/profile/update', [UserProfileController::class, 'updateProfile']);
});

View File

@@ -75,6 +75,20 @@ Route::prefix('admin')
Route::get('/requests/reject/{id}', [UserRequestController::class, 'reject'])
->name('admin.requests.reject');
// PROFILE UPDATE REQUESTS
Route::get('/profile-update-requests',
[UserRequestController::class, 'profileUpdateRequests']
)->name('admin.profile.requests');
Route::get('/profile-update/approve/{id}',
[UserRequestController::class, 'approveProfileUpdate']
)->name('admin.profile.approve');
Route::get('/profile-update/reject/{id}',
[UserRequestController::class, 'rejectProfileUpdate']
)->name('admin.profile.reject');
// ---------------------------
// MARK LIST
@@ -185,8 +199,7 @@ Route::prefix('admin')
Route::post('/invoices/{invoice}/installments', [AdminInvoiceController::class, 'storeInstallment'])
->name('admin.invoice.installment.store');
Route::post('/invoices/{id}/installment', [AdminInvoiceController::class, 'storeInstallment'])
->name('admin.invoice.installment.store');
Route::delete('/installment/{id}', [AdminInvoiceController::class, 'deleteInstallment'])
->name('admin.invoice.installment.delete');