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:
Graphictoria 2023-01-06 20:57:15 -05:00
parent 241f2deb16
commit 0083a01d85
27 changed files with 545 additions and 120 deletions

View File

@ -17,7 +17,7 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
$schedule->job(new UpdateUsageCounters)->everyMinute();
$schedule->job(new UpdateUsageCounters)->everyMinute()->evenInMaintenanceMode();
}
/**

View File

@ -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',

View File

@ -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) {

View File

@ -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()

View File

@ -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');
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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,
];

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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');
}
}

View File

@ -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;
}

View File

@ -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'
];
}

View File

@ -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();

View File

@ -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');
}
}

View File

@ -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();

View File

@ -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');
}
};

View File

@ -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');
}
};

View File

@ -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');
}
};

View File

@ -18,7 +18,8 @@ class DatabaseSeeder extends Seeder
WebConfigurationSeeder::class,
AssetTypeSeeder::class,
UsageCounterSeeder::class,
RolesetSeeder::class
RolesetSeeder::class,
PunishmentTypeSeeder::class
//FFlagSeeder::class
]);
}

View File

@ -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']);
}
}

View File

@ -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&nbsp;</span>
@ -394,7 +424,7 @@ class Shop extends Component {
<span>&nbsp;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>
:

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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');
});