Catalog changes and punishments.
- Updated pagination to 35 assets per page in order to fit with newer card scaling. - Implemented the ability to go to traverse pages in the shop. - Allowed the CPU/Memory usage updater job to run when the site is under maintenance. - User punishments. - Revamped punishment notice page. - Created middleware to redirect to punishment notice page when a punishment is active. - Completed moderation status label on user admin/search pages. - Added routes for punishments. - Fixed user homepage button on user admin page. - Added punishments section on user admin page. - Removed legacy bans. - Prevent banned user thumbnails from being rendered.
This commit is contained in:
parent
241f2deb16
commit
0083a01d85
|
|
@ -17,7 +17,7 @@ class Kernel extends ConsoleKernel
|
|||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
$schedule->job(new UpdateUsageCounters)->everyMinute();
|
||||
$schedule->job(new UpdateUsageCounters)->everyMinute()->evenInMaintenanceMode();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ class CommentsController extends Controller
|
|||
];
|
||||
|
||||
foreach($comments as $comment) {
|
||||
// TODO: XlXi: user profile link
|
||||
$poster = [
|
||||
'name' => $comment->user->username,
|
||||
'thumbnail' => 'https://www.virtubrick.local/images/testing/headshot.png',
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class ShopController extends Controller
|
|||
|
||||
$assets = self::getAssets($valid['assetTypeId'], (isset($valid['gearGenreId']) ? $valid['gearGenreId'] : null));
|
||||
$assets = $assets->orderByDesc('created_at')
|
||||
->paginate(30);
|
||||
->paginate(35);
|
||||
|
||||
$data = [];
|
||||
foreach($assets as $asset) {
|
||||
|
|
|
|||
|
|
@ -31,11 +31,10 @@ class ThumbnailController extends Controller
|
|||
|
||||
private function userValidationRules()
|
||||
{
|
||||
// TODO: Fail validation if user is moderated.
|
||||
return [
|
||||
'id' => [
|
||||
'required',
|
||||
Rule::exists('App\Models\User', 'id')
|
||||
Rule::exists('App\Models\User', 'id'),
|
||||
],
|
||||
'position' => ['sometimes', 'regex:/(Full|Bust)/i'],
|
||||
'type' => 'regex:/(3D|2D)/i'
|
||||
|
|
@ -55,7 +54,13 @@ class ThumbnailController extends Controller
|
|||
$valid['type'] = strtolower($valid['type']);
|
||||
|
||||
if($renderType == 'User') {
|
||||
if($valid['position'] == null)
|
||||
if($model->hasActivePunishment() && $model->getPunishment()->isDeletion())
|
||||
{
|
||||
$validator->errors()->add('id', 'User is moderated');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
if(!array_key_exists('position', $valid))
|
||||
$valid['position'] = 'Full';
|
||||
|
||||
$valid['position'] = strtolower($valid['position']);
|
||||
|
|
@ -109,9 +114,9 @@ class ThumbnailController extends Controller
|
|||
return $this->handleRender($request, 'Asset');
|
||||
}
|
||||
|
||||
public function renderUser()
|
||||
public function renderUser(Request $request)
|
||||
{
|
||||
return handleRender($request, 'User');
|
||||
return $this->handleRender($request, 'User');
|
||||
}
|
||||
|
||||
public function tryAsset()
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Web\Auth;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class UserModerationController extends Controller
|
||||
{
|
||||
public function create()
|
||||
{
|
||||
return view('web.auth.moderated');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Web;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class ModerationController extends Controller
|
||||
{
|
||||
public function notice(Request $request)
|
||||
{
|
||||
return view('web.auth.moderated')->with('punishment', Auth::user()->getPunishment());
|
||||
}
|
||||
|
||||
public function reactivate(Request $request)
|
||||
{
|
||||
$punishment = Auth::user()->getPunishment();
|
||||
if(!$punishment || !$punishment->expired())
|
||||
return redirect()->back();
|
||||
|
||||
$punishment->active = false;
|
||||
$punishment->save();
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,9 @@ class ProfileController extends Controller
|
|||
{
|
||||
protected function index(Request $request, User $user)
|
||||
{
|
||||
if($user->hasActivePunishment() && $user->getPunishment()->isDeletion())
|
||||
abort(404);
|
||||
|
||||
return view('web.user.profile')->with([
|
||||
'title' => sprintf('Profile of %s', $user->username),
|
||||
'user' => $user
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class Kernel extends HttpKernel
|
|||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -43,14 +43,15 @@ class Kernel extends HttpKernel
|
|||
// XlXi: Yeah no, the double session protector was stupid.
|
||||
//\App\Http\Middleware\DoubleSessionProtector::class, // Prevents DDoS attacks.
|
||||
\App\Http\Middleware\DailyReward::class,
|
||||
\App\Http\Middleware\LastSeenMiddleware::class
|
||||
\App\Http\Middleware\LastSeenMiddleware::class,
|
||||
\App\Http\Middleware\UserPunishmentMiddleware::class
|
||||
],
|
||||
|
||||
'api' => [
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
|
||||
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
|
||||
\App\Http\Middleware\UserPunishmentMiddleware::class
|
||||
],
|
||||
];
|
||||
|
||||
|
|
@ -74,7 +75,6 @@ class Kernel extends HttpKernel
|
|||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
|
||||
'roleset' => \App\Http\Middleware\Roleset::class,
|
||||
'banned' => \App\Http\Middleware\CheckBan::class,
|
||||
'lastseen' => \App\Http\Middleware\LastSeenMiddleware::class,
|
||||
'csrf' => \App\Http\Middleware\VerifyCsrfToken::class,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CheckBan
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if(Auth::check() && Auth::user()->banId != null) {
|
||||
if($request->route()->getName() != 'moderation.notice' && $request->route()->getName() != 'logout') {
|
||||
return redirect()
|
||||
->to(route('moderation.notice', [], 302));
|
||||
}
|
||||
} else {
|
||||
return redirect('/', 302);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserPunishmentMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$isPunishmentRoute = str_starts_with($request->route()->getName(), 'punishment.');
|
||||
if(Auth::user() && Auth::user()->hasActivePunishment())
|
||||
{
|
||||
if($isPunishmentRoute || $request->route()->getName() == 'auth.logout')
|
||||
return $next($request);
|
||||
|
||||
if(in_array('api', $request->route()->middleware()))
|
||||
{
|
||||
if($request->route()->getName() == 'content') // cdn.virtubrick.net
|
||||
return $next($request);
|
||||
|
||||
return response(['errors' => [['code' => 0, 'message' => 'User is moderated']]], 403)
|
||||
->header('Cache-Control', 'private')
|
||||
->header('Content-Type', 'application/json; charset=utf-8');
|
||||
}
|
||||
|
||||
// Not an API route.
|
||||
if(!$isPunishmentRoute)
|
||||
return redirect()->route('punishment.notice', ['ReturnUrl' => url()->full()]);
|
||||
}
|
||||
elseif($isPunishmentRoute)
|
||||
{
|
||||
$returnUrl = $request->input('ReturnUrl');
|
||||
|
||||
if(!$returnUrl)
|
||||
$returnUrl = '/';
|
||||
|
||||
return redirect($returnUrl);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Punishment extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'expiration' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime'
|
||||
];
|
||||
|
||||
public function punishment_type()
|
||||
{
|
||||
return $this->belongsTo(PunishmentType::class, 'punishment_type_id');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function moderator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'moderator_id');
|
||||
}
|
||||
|
||||
public function pardoner()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'pardoner_id');
|
||||
}
|
||||
|
||||
public function context()
|
||||
{
|
||||
return $this->hasMany(PunishmentContext::class, 'punishment_id');
|
||||
}
|
||||
|
||||
public function expired()
|
||||
{
|
||||
if($this->user->hasRoleset('Owner'))
|
||||
return true;
|
||||
|
||||
if(!$this->expiration)
|
||||
return false;
|
||||
|
||||
return !$this->isDeletion() && Carbon::now()->greaterThan($this->expiration);
|
||||
}
|
||||
|
||||
public function isDeletion()
|
||||
{
|
||||
return $this->punishment_type->time === null;
|
||||
}
|
||||
|
||||
public function reviewed()
|
||||
{
|
||||
return $this->created_at->isoFormat('lll');
|
||||
}
|
||||
|
||||
public function expirationStr()
|
||||
{
|
||||
if(!$this->expiration)
|
||||
return 'Never';
|
||||
|
||||
return $this->created_at->isoFormat('lll');
|
||||
}
|
||||
|
||||
public static function activeFor($userId)
|
||||
{
|
||||
return self::where('user_id', $userId)
|
||||
->where('active', true)
|
||||
->orderByDesc('id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PunishmentContext extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PunishmentType extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'label',
|
||||
'time'
|
||||
];
|
||||
}
|
||||
|
|
@ -132,6 +132,21 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
return false;
|
||||
}
|
||||
|
||||
public function hasActivePunishment()
|
||||
{
|
||||
return Punishment::activeFor($this->id)->exists();
|
||||
}
|
||||
|
||||
public function getPunishment()
|
||||
{
|
||||
return Punishment::activeFor($this->id)->first();
|
||||
}
|
||||
|
||||
public function punishments()
|
||||
{
|
||||
return $this->hasMany(Punishment::class, 'user_id');
|
||||
}
|
||||
|
||||
public function _hasRolesetInternal($roleName)
|
||||
{
|
||||
$roleset = Roleset::where('Name', $roleName)->first();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Components\Admin;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ModerationStatus extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View|\Closure|string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('components.admin.moderation-status');
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,6 @@ return new class extends Migration
|
|||
$table->dateTime('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->unsignedBigInteger('banId')->nullable();
|
||||
|
||||
$table->string('biography')->nullable();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('punishments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedTinyInteger('punishment_type_id');
|
||||
$table->boolean('active');
|
||||
$table->string('user_note');
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->unsignedBigInteger('moderator_id');
|
||||
$table->unsignedBigInteger('pardoner_id')->nullable();
|
||||
$table->timestamp('expiration')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('punishments');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('punishment_types', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('label');
|
||||
$table->integer('time')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('punishment_types');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('punishment_contexts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedBigInteger('punishment_id');
|
||||
$table->string('user_note');
|
||||
$table->longText('description')->nullable();
|
||||
$table->string('content_hash')->nullable()->comment('Will display an image from the CDN.');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('punishment_contexts');
|
||||
}
|
||||
};
|
||||
|
|
@ -18,7 +18,8 @@ class DatabaseSeeder extends Seeder
|
|||
WebConfigurationSeeder::class,
|
||||
AssetTypeSeeder::class,
|
||||
UsageCounterSeeder::class,
|
||||
RolesetSeeder::class
|
||||
RolesetSeeder::class,
|
||||
PunishmentTypeSeeder::class
|
||||
//FFlagSeeder::class
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
use App\Models\PunishmentType;
|
||||
|
||||
class PunishmentTypeSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
PunishmentType::create(['label' => 'Reminder', 'time' => 0]);
|
||||
PunishmentType::create(['label' => 'Warning', 'time' => 0]);
|
||||
PunishmentType::create(['label' => 'Banned for 1 Day', 'time' => 1]);
|
||||
PunishmentType::create(['label' => 'Banned for 3 Days', 'time' => 3]);
|
||||
PunishmentType::create(['label' => 'Banned for 7 Days', 'time' => 7]);
|
||||
PunishmentType::create(['label' => 'Banned for 14 Days', 'time' => 14]);
|
||||
PunishmentType::create(['label' => 'Account Deleted']);
|
||||
}
|
||||
}
|
||||
|
|
@ -150,7 +150,9 @@ class ShopCategoryButton extends Component {
|
|||
}
|
||||
|
||||
handleClick() {
|
||||
this.props.navigateCategory(this.props.id, this.data);
|
||||
this.props.setPage(1, true, () => {
|
||||
this.props.navigateCategory(this.props.id, this.data);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -177,7 +179,7 @@ class ShopCategories extends Component {
|
|||
return (
|
||||
<div className="virtubrick-shop-categories">
|
||||
<h5>Category</h5>
|
||||
<ShopCategoryButton id="all" label="All Items" getCategoryAssetTypeByLabel={this.props.getCategoryAssetTypeByLabel} getCategoryAssetTypeIds={this.props.getCategoryAssetTypeIds} navigateCategory={this.props.navigateCategory} shopState={this.props.shopState} />
|
||||
<ShopCategoryButton id="all" label="All Items" getCategoryAssetTypeByLabel={this.props.getCategoryAssetTypeByLabel} getCategoryAssetTypeIds={this.props.getCategoryAssetTypeIds} navigateCategory={this.props.navigateCategory} shopState={this.props.shopState} setPage={this.props.setPage} />
|
||||
<ul className="list-unstyled ps-0">
|
||||
{
|
||||
Object.keys(shopCategories).map((categoryName, index) =>
|
||||
|
|
@ -185,10 +187,10 @@ class ShopCategories extends Component {
|
|||
<a className="text-decoration-none fw-normal align-items-center virtubrick-list-dropdown" data-bs-toggle="collapse" data-bs-target={`#${makeCategoryId(categoryName, 'collapse')}`} aria-expanded={(index === 0 ? 'true' : 'false')} href="#">{ categoryName }</a>
|
||||
<div className={classNames({'collapse': true, 'show': (index === 0)})} id={makeCategoryId(categoryName, 'collapse')}>
|
||||
<ul className="btn-toggle-nav list-unstyled fw-normal small">
|
||||
<li><ShopCategoryButton id={makeCategoryId(`all-${categoryName}`, 'type')} label={`All ${categoryName}`} categoryName={categoryName} getCategoryAssetTypeByLabel={this.props.getCategoryAssetTypeByLabel} getCategoryAssetTypeIds={this.props.getCategoryAssetTypeIds} navigateCategory={this.props.navigateCategory} shopState={this.props.shopState} /></li>
|
||||
<li><ShopCategoryButton id={makeCategoryId(`all-${categoryName}`, 'type')} label={`All ${categoryName}`} categoryName={categoryName} getCategoryAssetTypeByLabel={this.props.getCategoryAssetTypeByLabel} getCategoryAssetTypeIds={this.props.getCategoryAssetTypeIds} navigateCategory={this.props.navigateCategory} shopState={this.props.shopState} setPage={this.props.setPage} /></li>
|
||||
{
|
||||
shopCategories[categoryName].map(({label, assetTypeId, gearGenreId}, index) =>
|
||||
<li><ShopCategoryButton id={makeCategoryId(`${label}-${categoryName}`, 'type')} label={label} categoryName={categoryName} getCategoryAssetTypeByLabel={this.props.getCategoryAssetTypeByLabel} getCategoryAssetTypeIds={this.props.getCategoryAssetTypeIds} navigateCategory={this.props.navigateCategory} shopState={this.props.shopState} /></li>
|
||||
<li><ShopCategoryButton id={makeCategoryId(`${label}-${categoryName}`, 'type')} label={label} categoryName={categoryName} getCategoryAssetTypeByLabel={this.props.getCategoryAssetTypeByLabel} getCategoryAssetTypeIds={this.props.getCategoryAssetTypeIds} navigateCategory={this.props.navigateCategory} shopState={this.props.shopState} setPage={this.props.setPage} /></li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
|
|
@ -271,10 +273,13 @@ class Shop extends Component {
|
|||
pageLoaded: true,
|
||||
pageNumber: null,
|
||||
pageCount: null,
|
||||
error: false
|
||||
error: false,
|
||||
dataMem: false
|
||||
};
|
||||
|
||||
this.navigateCategory = this.navigateCategory.bind(this);
|
||||
this.incrementPage = this.incrementPage.bind(this);
|
||||
this.setPage = this.setPage.bind(this);
|
||||
}
|
||||
|
||||
getCategoryAssetTypeIds(categoryName) {
|
||||
|
|
@ -304,14 +309,23 @@ class Shop extends Component {
|
|||
return assetType;
|
||||
}
|
||||
|
||||
navigateCategory(categoryId, data) {
|
||||
this.setState({selectedCategoryId: categoryId, pageLoaded: false});
|
||||
navigateCategory(categoryId, dataraw) {
|
||||
if(this.state.pageLoaded == false) return;
|
||||
|
||||
this.setState({selectedCategoryId: categoryId, dataMem: dataraw, pageLoaded: false});
|
||||
|
||||
let url = buildGenericApiUrl('api', 'shop/v1/list-json');
|
||||
|
||||
if (this.state.pageNumber == null || this.state.pageNumber == 1)
|
||||
this.setState({pageNumber: 1});
|
||||
|
||||
let paramIterator = 0;
|
||||
let data = {...dataraw, page: this.state.pageNumber};
|
||||
Object.keys(data).filter(key => {
|
||||
if (key == 'label')
|
||||
return false;
|
||||
else if (key == 'page' && (data[key] == null || data[key] == 1))
|
||||
return false;
|
||||
return true;
|
||||
}).map(key => {
|
||||
url += ((paramIterator++ == 0 ? '?' : '&') + `${key}=${data[key]}`);
|
||||
|
|
@ -321,7 +335,7 @@ class Shop extends Component {
|
|||
.then(res => {
|
||||
const items = res.data;
|
||||
|
||||
this.setState({ pageItems: items.data, pageCount: items.pages, pageNumber: 1, pageLoaded: true, error: false });
|
||||
this.setState({ pageItems: items.data, pageCount: items.pages, pageLoaded: true, error: false });
|
||||
}).catch(err => {
|
||||
const data = err.response.data;
|
||||
|
||||
|
|
@ -334,6 +348,22 @@ class Shop extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
incrementPage(amount) {
|
||||
this.setPage(this.state.pageNumber + amount);
|
||||
}
|
||||
|
||||
setPage(page, bypass = false, callback) {
|
||||
if(!bypass && this.state.pageLoaded == false) return;
|
||||
|
||||
this.setState({pageNumber: page}, () => {
|
||||
if(callback)
|
||||
callback();
|
||||
|
||||
if(!bypass)
|
||||
this.navigateCategory(this.state.selectedCategoryId, this.state.dataMem);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="container-lg my-2">
|
||||
|
|
@ -351,7 +381,7 @@ class Shop extends Component {
|
|||
</div>
|
||||
<div className="row">
|
||||
<div className="col-md-2">
|
||||
<ShopCategories getCategoryAssetTypeByLabel={this.getCategoryAssetTypeByLabel} getCategoryAssetTypeIds={this.getCategoryAssetTypeIds} navigateCategory={this.navigateCategory} shopState={this.state} />
|
||||
<ShopCategories getCategoryAssetTypeByLabel={this.getCategoryAssetTypeByLabel} getCategoryAssetTypeIds={this.getCategoryAssetTypeIds} navigateCategory={this.navigateCategory} shopState={this.state} setPage={this.setPage} />
|
||||
</div>
|
||||
<div className="col-md-10 d-flex flex-column">
|
||||
<div className="card p-3">
|
||||
|
|
@ -386,7 +416,7 @@ class Shop extends Component {
|
|||
this.state.pageCount > 1 ?
|
||||
<ul className="list-inline mx-auto mt-3">
|
||||
<li className="list-inline-item">
|
||||
<button className="btn btn-secondary" disabled={(this.state.pageNumber <= 1) ? true : null}><i className="fa-solid fa-angle-left"></i></button>
|
||||
<button className="btn btn-secondary" disabled={(this.state.pageNumber <= 1) ? true : null} onClick={ () => this.incrementPage(-1) }><i className="fa-solid fa-angle-left"></i></button>
|
||||
</li>
|
||||
<li className="list-inline-item virtubrick-paginator">
|
||||
<span>Page </span>
|
||||
|
|
@ -394,7 +424,7 @@ class Shop extends Component {
|
|||
<span> of { this.state.pageCount || '???' }</span>
|
||||
</li>
|
||||
<li className="list-inline-item">
|
||||
<button className="btn btn-secondary" disabled={(this.state.pageNumber >= this.state.pageCount) ? true : null}><i className="fa-solid fa-angle-right"></i></button>
|
||||
<button className="btn btn-secondary" disabled={(this.state.pageNumber >= this.state.pageCount) ? true : null} onClick={ () => this.incrementPage(1) }><i className="fa-solid fa-angle-right"></i></button>
|
||||
</li>
|
||||
</ul>
|
||||
:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
@props([
|
||||
'user'
|
||||
])
|
||||
|
||||
@php
|
||||
$color = 'text-';
|
||||
$label = 'Unknown';
|
||||
if($user->hasActivePunishment())
|
||||
{
|
||||
$color .= 'danger';
|
||||
$label = $user->getPunishment()->punishment_type->label;
|
||||
}
|
||||
else
|
||||
{
|
||||
$color .= 'success';
|
||||
$label = 'OK';
|
||||
}
|
||||
@endphp
|
||||
<p class="{{ $color }}">{{ $label }}</p>
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
@endif
|
||||
<x-admin.user-admin-label label="Username">{{ $user->username }}</x-admin.user-admin-label>
|
||||
<x-admin.user-admin-label label="Previous User Names"><b>TODO</b></x-admin.user-admin-label>
|
||||
<x-admin.user-admin-label label="Moderation Status"><span class="text-success">OK (TODO)</span></x-admin.user-admin-label>
|
||||
<x-admin.user-admin-label label="Moderation Status"><x-admin.moderation-status :user="$user" /></x-admin.user-admin-label>
|
||||
<x-admin.user-admin-label label="User Id">
|
||||
<x-admin.user-search-input id="userid" definition="User ID" :value="$user->id" :nolabel=true />
|
||||
</x-admin.user-admin-label>
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
<img src="{{ asset('/images/testing/avatar.png') }}" width="200" height="200" class="img-fluid vb-charimg" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<a href="#" class="text-decoration-none">User Homepage</a><br/>
|
||||
<a href="{{ $user->getProfileUrl() }}" class="text-decoration-none">User Homepage</a><br/>
|
||||
<a href="#" class="text-decoration-none">Moderate User</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -90,16 +90,24 @@
|
|||
<th scope="col">Moderator</th>
|
||||
<th scope="col">Created</th>
|
||||
<th scope="col">Expiration</th>
|
||||
<th scope="col">Acknowledged</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="col">1</th>
|
||||
<th scope="col">1 day, perm, etc...</th>
|
||||
<th scope="col">Joe</th>
|
||||
<th scope="col">1/2/3 4:5 6</th>
|
||||
<th scope="col">1/2/3 4:5 6</th>
|
||||
</tr>
|
||||
@foreach($user->punishments as $punishment)
|
||||
<tr>
|
||||
<th scope="col">{{ $punishment->id }}</th>
|
||||
<th scope="col">{{ $punishment->punishment_type->label }}</th>
|
||||
<th scope="col">
|
||||
<a href="{{ route('admin.useradmin', ['ID' => $punishment->moderator->id]) }}" class="text-decoration-none">
|
||||
<x-user-circle :user="$punishment->moderator" :size=24 />
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col">{{ $punishment->reviewed() }}</th>
|
||||
<th scope="col">{{ $punishment->expirationStr() }}</th>
|
||||
<th scope="col">{{ $punishment->active ? 'No' : 'Yes' }}</th>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
</th>
|
||||
<th scope="col">{{ $user->id }}</th>
|
||||
<th scope="col">{{ Auth::user()->hasRoleset('Owner') ? $user->email : $user->getCensoredEmail() }}</th>
|
||||
<th scope="col"><p class="text-success">OK (TODO)</p></th>
|
||||
<th scope="col"><x-admin.moderation-status :user="$user" /></th>
|
||||
<th scope="col">
|
||||
@if($rolesetCount > 0)
|
||||
@php
|
||||
|
|
|
|||
|
|
@ -1,42 +1,62 @@
|
|||
@php
|
||||
$noFooter = true;
|
||||
$noNav = true;
|
||||
@endphp
|
||||
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Moderation Notice')
|
||||
@section('theme', 'light')
|
||||
@nonav
|
||||
@nofooter
|
||||
|
||||
@section('content')
|
||||
<div class="container m-auto">
|
||||
<x-card class="virtubrick-moderation-card">
|
||||
<x-slot name="title">
|
||||
MODERATION NOTICE
|
||||
</x-slot>
|
||||
<x-slot name="body">
|
||||
<div class="p-2 mb-2 d-flex flex-column justify-content-center">
|
||||
<p>Your account has been suspended for violating our Terms of Service.</p>
|
||||
<div class="my-3">
|
||||
<p><b>Suspention Date:</b> 5/6/2022 9:35 PM</p>
|
||||
<p><b>Note:</b> testing</p>
|
||||
</div>
|
||||
<div class="card p-3 virtubrick-moderation-card">
|
||||
<h3>{{ $punishment->punishment_type->label }}</h3>
|
||||
<p>
|
||||
Your account has been {{ $punishment->isDeletion() ? 'closed ' : 'temporarily restricted' }} for violating our Terms of Service.
|
||||
@if(!$punishment->isDeletion())
|
||||
Your account will be terminated if you do not abide by the rules.
|
||||
@endif
|
||||
</p>
|
||||
|
||||
<div class="my-3">
|
||||
<p><b>Reviewed:</b> {{ $punishment->reviewed() }}</p>
|
||||
<p><b>Moderator Note:</b> {{ $punishment->user_note }}</p>
|
||||
</div>
|
||||
|
||||
@foreach($punishment->context as $context)
|
||||
<div class="card bg-secondary p-2 mb-2 border-1">
|
||||
<p><b>Reason:</b> {{ $context->user_note }}</p>
|
||||
@if($context->description)
|
||||
<p><b>Offensive Item:</b> {{ $context->description }}</p>
|
||||
@endif
|
||||
@if($context->content_hash)
|
||||
<img src="{{ route('content', $context->content_hash) }}" class="img-fluid" width="210" height="210"/>
|
||||
@endif
|
||||
</div>
|
||||
</x-slot>
|
||||
<x-slot name="footer">
|
||||
<p>By checking the "I Agree" checkbox below, you agree to abide by {{ config('app.name') }}'s Terms of Service. Your account will be permantently suspended if you continue breaking the Terms of Service.</p>
|
||||
<form>
|
||||
<div class="my-2">
|
||||
<input class="form-check-input" type="checkbox" value="" id="agree" name="agree">
|
||||
<label class="form-check-label" for="agree">
|
||||
I Agree
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-primary">REACTIVATE</button>
|
||||
</form>
|
||||
@endforeach
|
||||
|
||||
<div class="text-center">
|
||||
@if($punishment->expired())
|
||||
<p>By checking the "I Agree" checkbox below, you agree to abide by {{ config('app.name') }}'s Terms of Service.</p>
|
||||
<form method="POST" action="{{ route('punishment.reactivate') }}" class="mt-2">
|
||||
@csrf
|
||||
<div class="mb-2">
|
||||
<input class="form-check-input" type="checkbox" value="" id="vb-reactivation-checkbox">
|
||||
<label class="form-check-label" for="vb-reactivation-checkbox">
|
||||
I Agree
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-success" id="vb-reactivation-button" disabled>Re-activate My Account</button>
|
||||
</form>
|
||||
<script>
|
||||
document.getElementById('vb-reactivation-checkbox').addEventListener('change', (event) => {
|
||||
document.getElementById('vb-reactivation-button').disabled = !event.currentTarget.checked;
|
||||
});
|
||||
</script>
|
||||
@elseif(!$punishment->isDeletion())
|
||||
<p>You will be able to reactivate your account in <b>{{ $punishment->expiration->diffForHumans(['syntax' => Carbon\CarbonInterface::DIFF_ABSOLUTE]) }}</b>.</p>
|
||||
@endif
|
||||
|
||||
<a class="btn btn-primary my-2" href="{{ route('auth.logout') }}">Logout</a>
|
||||
|
||||
<p>You will be able to reactivate your account in <b>0 Seconds</b>.</p>
|
||||
<p class="text-muted">If you believe you have been unfairly moderated, please contact us at contact us at <a href="mailto:support@virtubrick.net" class="fw-bold text-decoration-none">support@virtubrick.net</a> and we'll be happy to help.</p>
|
||||
</x-slot>
|
||||
</x-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -74,13 +74,11 @@ Route::group(['as' => 'admin.', 'prefix' => 'admin'], function() {
|
|||
});
|
||||
|
||||
Route::group(['as' => 'auth.', 'namespace' => 'Auth'], function() {
|
||||
Route::group(['as' => 'protection.', 'prefix' => 'request-blocked'], function() {
|
||||
Route::get('/', 'DoubleSessionBlockController@index')->name('index');
|
||||
Route::post('/', 'DoubleSessionBlockController@store')->name('bypass');
|
||||
});
|
||||
//Route::group(['as' => 'protection.', 'prefix' => 'request-blocked'], function() {
|
||||
// Route::get('/', 'DoubleSessionBlockController@index')->name('index');
|
||||
// Route::post('/', 'DoubleSessionBlockController@store')->name('bypass');
|
||||
//});
|
||||
|
||||
Route::get('/moderation-notice', 'UserModerationController@index')->middleware(['auth', 'banned'])->name('moderation.notice');
|
||||
|
||||
Route::middleware('guest')->group(function () {
|
||||
Route::group(['as' => 'register.', 'prefix' => 'register'], function() {
|
||||
Route::get('/', 'RegisteredUserController@index')->name('index');
|
||||
|
|
@ -120,6 +118,12 @@ Route::group(['as' => 'auth.', 'namespace' => 'Auth'], function() {
|
|||
});
|
||||
});
|
||||
|
||||
Route::group(['as' => 'punishment.', 'prefix' => 'membership'], function() {
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::get('/not-approved', 'ModerationController@notice')->name('notice');
|
||||
Route::post('/not-approved', 'ModerationController@reactivate')->name('reactivate');
|
||||
});
|
||||
});
|
||||
|
||||
Route::withoutMiddleware(['csrf'])->group(function () {
|
||||
Route::group(['as' => 'client.'], function() {
|
||||
|
|
@ -129,4 +133,8 @@ Route::withoutMiddleware(['csrf'])->group(function () {
|
|||
Route::post('/PlaceLauncher', 'ClientGameController@placeLauncher')->name('placelauncher');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Route::fallback(function() {
|
||||
return view('errors.404');
|
||||
});
|
||||
Loading…
Reference in New Issue