feat: Inertia profile settings page, Studio edit redesign, EGS, Nova UI components\n\n- Redesign /dashboard/profile as Inertia React page (Settings/ProfileEdit)\n with SettingsLayout sidebar, Nova UI components (TextInput, Textarea,\n Toggle, Select, RadioGroup, Modal, Button), avatar drag-and-drop,\n password change, and account deletion sections\n- Redesign Studio artwork edit page with two-column layout, Nova components,\n integrated TagPicker, and version history modal\n- Add shared MarkdownEditor component\n- Add Early-Stage Growth System (EGS): SpotlightEngine, FeedBlender,\n GridFiller, AdaptiveTimeWindow, ActivityLayer, admin panel\n- Fix upload category/tag persistence (V1+V2 paths)\n- Fix tag source enum, category tree display, binding resolution\n- Add settings.jsx Vite entry, settings.blade.php wrapper\n- Update ProfileController with JSON response support for API calls\n- Various route fixes (profile.edit, toolbar settings link)"
This commit is contained in:
@@ -121,7 +121,94 @@ class ProfileController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ProfileUpdateRequest $request, \App\Services\AvatarService $avatarService): RedirectResponse
|
||||
/**
|
||||
* Inertia-powered profile edit page (Settings/ProfileEdit).
|
||||
*/
|
||||
public function editSettings(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
// Parse birth date parts
|
||||
$birthDay = null;
|
||||
$birthMonth = null;
|
||||
$birthYear = null;
|
||||
|
||||
// Merge modern user_profiles data
|
||||
$profileData = [];
|
||||
try {
|
||||
if (Schema::hasTable('user_profiles')) {
|
||||
$profile = DB::table('user_profiles')->where('user_id', $user->id)->first();
|
||||
if ($profile) {
|
||||
$profileData = (array) $profile;
|
||||
if (isset($profile->website)) $user->homepage = $profile->website;
|
||||
if (isset($profile->about)) $user->about_me = $profile->about;
|
||||
if (isset($profile->birthdate)) $user->birth = $profile->birthdate;
|
||||
if (isset($profile->gender)) $user->gender = $profile->gender;
|
||||
if (isset($profile->country_code)) $user->country_code = $profile->country_code;
|
||||
if (isset($profile->signature)) $user->signature = $profile->signature;
|
||||
if (isset($profile->description)) $user->description = $profile->description;
|
||||
if (isset($profile->mlist)) $user->mlist = $profile->mlist;
|
||||
if (isset($profile->friend_upload_notice)) $user->friend_upload_notice = $profile->friend_upload_notice;
|
||||
if (isset($profile->auto_post_upload)) $user->auto_post_upload = $profile->auto_post_upload;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
if (!empty($user->birth)) {
|
||||
try {
|
||||
$dt = \Carbon\Carbon::parse($user->birth);
|
||||
$birthDay = $dt->format('d');
|
||||
$birthMonth = $dt->format('m');
|
||||
$birthYear = $dt->format('Y');
|
||||
} catch (\Throwable $e) {}
|
||||
}
|
||||
|
||||
// Country list
|
||||
$countries = collect();
|
||||
try {
|
||||
if (Schema::hasTable('country_list')) {
|
||||
$countries = DB::table('country_list')->orderBy('country_name')->get();
|
||||
} elseif (Schema::hasTable('countries')) {
|
||||
$countries = DB::table('countries')->orderBy('name')->get();
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
// Avatar URL
|
||||
$avatarHash = $profileData['avatar_hash'] ?? $user->icon ?? null;
|
||||
$avatarUrl = !empty($avatarHash)
|
||||
? AvatarUrl::forUser((int) $user->id, $avatarHash, 128)
|
||||
: AvatarUrl::default();
|
||||
|
||||
return Inertia::render('Settings/ProfileEdit', [
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'email' => $user->email,
|
||||
'name' => $user->name,
|
||||
'homepage' => $user->homepage ?? $user->website ?? null,
|
||||
'about_me' => $user->about_me ?? $user->about ?? null,
|
||||
'signature' => $user->signature ?? null,
|
||||
'description' => $user->description ?? null,
|
||||
'gender' => $user->gender ?? null,
|
||||
'country_code' => $user->country_code ?? null,
|
||||
'mlist' => $user->mlist ?? false,
|
||||
'friend_upload_notice' => $user->friend_upload_notice ?? false,
|
||||
'auto_post_upload' => $user->auto_post_upload ?? false,
|
||||
'username_changed_at' => $user->username_changed_at,
|
||||
],
|
||||
'avatarUrl' => $avatarUrl,
|
||||
'birthDay' => $birthDay,
|
||||
'birthMonth' => $birthMonth,
|
||||
'birthYear' => $birthYear,
|
||||
'countries' => $countries->values(),
|
||||
'flash' => [
|
||||
'status' => session('status'),
|
||||
'error' => session('error'),
|
||||
],
|
||||
])->rootView('settings');
|
||||
}
|
||||
|
||||
public function update(ProfileUpdateRequest $request, \App\Services\AvatarService $avatarService): RedirectResponse|JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
@@ -144,18 +231,22 @@ class ProfileController extends Controller
|
||||
'current_username' => $currentUsername,
|
||||
]);
|
||||
|
||||
return Redirect::back()->withErrors([
|
||||
'username' => 'This username is too similar to a reserved name and requires manual approval.',
|
||||
]);
|
||||
$error = ['username' => ['This username is too similar to a reserved name and requires manual approval.']];
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['errors' => $error], 422);
|
||||
}
|
||||
return Redirect::back()->withErrors($error);
|
||||
}
|
||||
|
||||
$cooldownDays = (int) config('usernames.rename_cooldown_days', 90);
|
||||
$isAdmin = method_exists($user, 'isAdmin') ? $user->isAdmin() : false;
|
||||
|
||||
if (! $isAdmin && $user->username_changed_at !== null && $user->username_changed_at->gt(now()->subDays($cooldownDays))) {
|
||||
return Redirect::back()->withErrors([
|
||||
'username' => "Username can only be changed once every {$cooldownDays} days.",
|
||||
]);
|
||||
$error = ['username' => ["Username can only be changed once every {$cooldownDays} days."]];
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['errors' => $error], 422);
|
||||
}
|
||||
return Redirect::back()->withErrors($error);
|
||||
}
|
||||
|
||||
$user->username = $incomingUsername;
|
||||
@@ -234,6 +325,9 @@ class ProfileController extends Controller
|
||||
try {
|
||||
$avatarService->storeFromUploadedFile($user->id, $request->file('avatar'));
|
||||
} catch (\Exception $e) {
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['errors' => ['avatar' => ['Avatar processing failed: ' . $e->getMessage()]]], 422);
|
||||
}
|
||||
return Redirect::back()->with('error', 'Avatar processing failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -274,12 +368,17 @@ class ProfileController extends Controller
|
||||
logger()->error('Profile update error: '.$e->getMessage());
|
||||
}
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
return Redirect::route('dashboard.profile')->with('status', 'profile-updated');
|
||||
}
|
||||
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
public function destroy(Request $request): RedirectResponse|JsonResponse
|
||||
{
|
||||
$request->validateWithBag('userDeletion', [
|
||||
$bag = $request->expectsJson() ? 'default' : 'userDeletion';
|
||||
$request->validateWithBag($bag, [
|
||||
'password' => ['required', 'current_password'],
|
||||
]);
|
||||
|
||||
@@ -292,10 +391,14 @@ class ProfileController extends Controller
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
public function password(Request $request): RedirectResponse
|
||||
public function password(Request $request): RedirectResponse|JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'current_password' => ['required', 'current_password'],
|
||||
@@ -306,6 +409,10 @@ class ProfileController extends Controller
|
||||
$user->password = Hash::make($request->input('password'));
|
||||
$user->save();
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
return Redirect::route('dashboard.profile')->with('status', 'password-updated');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user