Shop, thumbnail, transactions, and admin changes.
- Fix shop view all button. - Fix internal server error when accessing an asset page while logged out. - User render busy icon. - Virtubrick server-side rendering paginator. - Pagination in user search. - Details of punishments on user admin page. - Updated some APIs to not return virtubrick.local thumbnail urls. - Compacted admin user search and turned it into a GET request. - Removed testing thumbnail from user circle component. - Removed testing thumbnail from user admin page. - Updated dashboard and profile to use new function for user thumbnails. - Grammar edit on user admin page for "user is a (power type)" label. - Made MarkdownHelper play nicely with site alerts. - Created a transactions page and APIs to go along with it. - Website can now render user thumbnails. - Reworked avatar/closeup render scripts. - Added a "userToJson" function to the user model for use in APIs that return user data.
This commit is contained in:
parent
c00e8ccc75
commit
ad71c712b9
|
|
@ -10,19 +10,31 @@ namespace App\Helpers;
|
|||
use Illuminate\Support\HtmlString;
|
||||
use League\CommonMark\Environment\Environment;
|
||||
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
|
||||
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
|
||||
use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
|
||||
use League\CommonMark\Extension\DefaultAttributes\DefaultAttributesExtension;
|
||||
use League\CommonMark\Extension\Table\TableExtension;
|
||||
use League\CommonMark\MarkdownConverter;
|
||||
|
||||
class MarkdownHelper
|
||||
{
|
||||
// XlXi: This bit was taken from https://github.com/laravel/framework/blob/b9203fca96960ef9cd8860cb4ec99d1279353a8d/src/Illuminate/Mail/Markdown.php line 106
|
||||
// TODO: XlXi: Add a non-nav alert mode for links.
|
||||
// XlXi: This bit was partially taken from https://github.com/laravel/framework/blob/b9203fca96960ef9cd8860cb4ec99d1279353a8d/src/Illuminate/Mail/Markdown.php line 106
|
||||
public static function parse($text) {
|
||||
$environment = new Environment([
|
||||
//
|
||||
'default_attributes' => [
|
||||
Link::class => [
|
||||
'class' => ['text-decoration-none']
|
||||
],
|
||||
Image::class => [
|
||||
'classes' => ['img-fluid']
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$environment->addExtension(new CommonMarkCoreExtension);
|
||||
$environment->addExtension(new TableExtension);
|
||||
$environment->addExtension(new DefaultAttributesExtension);
|
||||
|
||||
$converter = new MarkdownConverter($environment);
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class CommentsController extends Controller
|
|||
foreach($comments as $comment) {
|
||||
$poster = [
|
||||
'name' => $comment->user->username,
|
||||
'thumbnail' => 'https://www.virtubrick.local/images/testing/headshot.png',
|
||||
'thumbnail' => $comment->user->getHeadshotImageUrl(),
|
||||
'url' => $comment->user->getProfileUrl()
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Friend;
|
||||
use App\Models\Shout;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class FeedController extends Controller
|
||||
{
|
||||
|
|
@ -31,19 +32,12 @@ class FeedController extends Controller
|
|||
];
|
||||
|
||||
foreach($postsQuery as $post) {
|
||||
// TODO: XlXi: icons/colors
|
||||
// TODO: XlXi: groups
|
||||
|
||||
$poster = [];
|
||||
if($post['poster_type'] == 'user') {
|
||||
$user = User::where('id', $post['poster_id'])->first();
|
||||
|
||||
$poster = [
|
||||
'type' => 'User',
|
||||
'name' => $user->username,
|
||||
'thumbnail' => 'https://www.virtubrick.local/images/testing/headshot.png',
|
||||
'url' => $user->getProfileUrl()
|
||||
];
|
||||
$poster = $user->userToJson();
|
||||
}
|
||||
|
||||
/* */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use App\Helpers\ValidationHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Transaction;
|
||||
use App\Models\TransactionType;
|
||||
use App\Models\User;
|
||||
|
||||
class MoneyController extends Controller
|
||||
{
|
||||
public function userSummary(Request $request)
|
||||
{
|
||||
$result = [
|
||||
'columns' => [],
|
||||
'total' => 0
|
||||
];
|
||||
|
||||
$dataPoints = [
|
||||
[
|
||||
'Name' => 'Item Purchases',
|
||||
'Points' => ['Purchases']
|
||||
],
|
||||
[
|
||||
'Name' => 'Sale of Goods',
|
||||
'Points' => ['Sales', 'Commissions']
|
||||
],
|
||||
[
|
||||
'Name' => 'Group Payouts',
|
||||
'Points' => ['Group Payouts']
|
||||
]
|
||||
];
|
||||
|
||||
foreach($dataPoints as $dataPoint)
|
||||
{
|
||||
$newColumn = ['name' => $dataPoint['Name'], 'total' => 0];
|
||||
|
||||
foreach($dataPoint['Points'] as $transactionType)
|
||||
{
|
||||
$newColumn['total'] += Transaction::where('user_id', Auth::user()->id)
|
||||
->where('transaction_type_id', TransactionType::IDFromType($transactionType))
|
||||
->where(function($query) use($request) {
|
||||
if(!$request->has('filter'))
|
||||
return $query;
|
||||
|
||||
$now = Carbon::now();
|
||||
switch($request->get('filter'))
|
||||
{
|
||||
case 'pastday':
|
||||
return $query->where('created_at', '>', $now->subDay());
|
||||
case 'pastweek':
|
||||
return $query->where('created_at', '>', $now->subWeek());
|
||||
case 'pastmonth':
|
||||
return $query->where('created_at', '>', $now->subMonth());
|
||||
case 'pastyear':
|
||||
return $query->where('created_at', '>', $now->subYear());
|
||||
default:
|
||||
return $query;
|
||||
}
|
||||
})
|
||||
->sum('delta');
|
||||
}
|
||||
|
||||
array_push($result['columns'], $newColumn);
|
||||
$result['total'] += $newColumn['total'];
|
||||
}
|
||||
|
||||
return response($result);
|
||||
}
|
||||
|
||||
public function userTransactions(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'filter' => ['required', 'in:purchases,sales,commissions,grouppayouts']
|
||||
]);
|
||||
|
||||
if($validator->fails())
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
|
||||
$valid = $validator->valid();
|
||||
|
||||
$resultData = [];
|
||||
$transactionType = 0;
|
||||
switch($valid['filter'])
|
||||
{
|
||||
case 'purchases':
|
||||
$transactionType = TransactionType::where('name', 'Purchases')->first();
|
||||
break;
|
||||
case 'sales':
|
||||
$transactionType = TransactionType::where('name', 'Sales')->first();
|
||||
break;
|
||||
case 'commissions':
|
||||
$transactionType = TransactionType::where('name', 'Commissions')->first();
|
||||
break;
|
||||
case 'grouppayouts':
|
||||
$transactionType = TransactionType::where('name', 'Group Payouts')->first();
|
||||
break;
|
||||
}
|
||||
|
||||
$transactions = Transaction::where(function($query) use($valid) {
|
||||
if($valid['filter'] == 'sales')
|
||||
return $query->where('seller_id', Auth::user()->id);
|
||||
return $query->where('user_id', Auth::user()->id);
|
||||
})
|
||||
->where('transaction_type_id', $transactionType->id)
|
||||
->orderByDesc('id')
|
||||
->cursorPaginate(30);
|
||||
$prevCursor = $transactions->previousCursor();
|
||||
$nextCursor = $transactions->nextCursor();
|
||||
|
||||
foreach($transactions as $transaction)
|
||||
{
|
||||
$user = null;
|
||||
if($valid['filter'] != 'sales')
|
||||
$user = $transaction->seller;
|
||||
else
|
||||
$user = $transaction->user;
|
||||
|
||||
$asset = null;
|
||||
if($transactionType->format != '')
|
||||
$asset = [
|
||||
'url' => route('shop.asset', ['asset' => $transaction->asset, 'assetName' => Str::slug($transaction->asset->name, '-')]),
|
||||
'name' => $transaction->asset->name
|
||||
];
|
||||
|
||||
array_push($resultData, [
|
||||
'date' => $transaction->created_at->isoFormat('lll'),
|
||||
'member' => $user->userToJson(),
|
||||
'description' => $transactionType->format,
|
||||
'amount' => $transaction->delta,
|
||||
'item' => $asset
|
||||
]);
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => $resultData,
|
||||
'prev_cursor' => ($prevCursor ? $prevCursor->encode() : null),
|
||||
'next_cursor' => ($nextCursor ? $nextCursor->encode() : null)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ class ThumbnailController extends Controller
|
|||
];
|
||||
}
|
||||
|
||||
private function handleRender(Request $request, string $renderType)
|
||||
private function handleRender(Request $request, string $renderType, bool $assetId = null)
|
||||
{
|
||||
$validator = Validator::make($request->all(), $this->{strtolower($renderType) . 'ValidationRules'}());
|
||||
|
||||
|
|
@ -64,6 +64,24 @@ class ThumbnailController extends Controller
|
|||
$valid['position'] = 'Full';
|
||||
|
||||
$valid['position'] = strtolower($valid['position']);
|
||||
|
||||
if($valid['position'] != 'full' && $valid['type'] == '3d')
|
||||
{
|
||||
$validator->errors()->add('type', 'Cannot render non-full avatar as 3D.');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
switch($valid['position'])
|
||||
{
|
||||
case 'full':
|
||||
if($model->thumbnail2DHash && $valid['type'] == '2d')
|
||||
return response(['status' => 'success', 'data' => route('content', $model->thumbnail2DHash)]);
|
||||
break;
|
||||
case 'bust':
|
||||
if($model->thumbnailBustHash && $valid['type'] == '2d')
|
||||
return response(['status' => 'success', 'data' => route('content', $model->thumbnailBustHash)]);
|
||||
break;
|
||||
}
|
||||
} elseif($renderType == 'Asset') {
|
||||
if($model->moderated)
|
||||
return response(['status' => 'success', 'data' => '/thumbs/DeletedThumbnail.png']);
|
||||
|
|
@ -78,16 +96,17 @@ class ThumbnailController extends Controller
|
|||
$validator->errors()->add('id', 'This asset cannot be rendered.');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
if($model->thumbnail2DHash && $valid['type'] == '2d')
|
||||
return response(['status' => 'success', 'data' => route('content', $model->thumbnail2DHash)]);
|
||||
}
|
||||
|
||||
|
||||
if($model->thumbnail2DHash && $valid['type'] == '2d')
|
||||
return response(['status' => 'success', 'data' => route('content', $model->thumbnail2DHash)]);
|
||||
|
||||
if($model->thumbnail3DHash && $valid['type'] == '3d')
|
||||
return response(['status' => 'success', 'data' => route('content', $model->thumbnail3DHash)]);
|
||||
|
||||
$trackerType = sprintf('%s%s', strtolower($renderType), $valid['type']);
|
||||
if($renderType == 'User' && $valid['position'] == 'bust')
|
||||
$trackerType .= 'bust';
|
||||
$tracker = RenderTracker::where('type', $trackerType)
|
||||
->where('target', $valid['id'])
|
||||
->where('created_at', '>', Carbon::now()->subMinute());
|
||||
|
|
@ -101,7 +120,7 @@ class ThumbnailController extends Controller
|
|||
ArbiterRender::dispatch(
|
||||
$tracker,
|
||||
$valid['type'] == '3d',
|
||||
($renderType == 'User' ? $valid['position'] : $model->typeString()),
|
||||
($renderType == 'User' ? $valid['position'] == 'full' ? 'Avatar' : 'Bust' : $model->typeString()),
|
||||
$model->id
|
||||
);
|
||||
}
|
||||
|
|
@ -119,8 +138,22 @@ class ThumbnailController extends Controller
|
|||
return $this->handleRender($request, 'User');
|
||||
}
|
||||
|
||||
public function tryAsset()
|
||||
public function tryAsset(Request $request)
|
||||
{
|
||||
//
|
||||
$validator = Validator::make($request->all(), [
|
||||
'id' => [
|
||||
'required',
|
||||
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
|
||||
return $query->where('moderated', false);
|
||||
})
|
||||
]
|
||||
]);
|
||||
|
||||
if($validator->fails())
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
|
||||
$valid = $validator->valid();
|
||||
|
||||
return $this->handleRender($request, 'User', $valid['id']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,44 +41,63 @@ class AdminController extends Controller
|
|||
}
|
||||
|
||||
// GET admin.usersearch
|
||||
function userSearch()
|
||||
function userSearch(Request $request)
|
||||
{
|
||||
$types = [
|
||||
'userid' => 'UserId',
|
||||
'username' => 'UserName',
|
||||
'ipaddress' => 'IpAddress'
|
||||
];
|
||||
|
||||
foreach($types as $type => &$func)
|
||||
{
|
||||
if($type == $request->has($type))
|
||||
return $this->{'userSearchQuery' . $func}($request);
|
||||
}
|
||||
|
||||
return view('web.admin.usersearch');
|
||||
}
|
||||
|
||||
// POST admin.usersearchquery
|
||||
function userSearchQuery(Request $request)
|
||||
function userSearchQueryUserId(Request $request)
|
||||
{
|
||||
if($request->has('userid-button'))
|
||||
{
|
||||
$request->validate([
|
||||
'userid-search' => ['required', 'int']
|
||||
]);
|
||||
return view('web.admin.usersearch')->with('users', User::where('id', $request->get('userid-search'))->get());
|
||||
}
|
||||
$request->validate([
|
||||
'userid' => ['required', 'int']
|
||||
]);
|
||||
|
||||
if($request->has('username-button'))
|
||||
{
|
||||
$request->validate([
|
||||
'username-search' => ['required', 'string']
|
||||
]);
|
||||
return view('web.admin.usersearch')->with('users', User::where('username', 'like', '%' . $request->get('username-search') . '%')->get());
|
||||
}
|
||||
$users = User::where('id', $request->get('userid'))
|
||||
->paginate(25)
|
||||
->appends($request->all());
|
||||
|
||||
if($request->has('ipaddress-button'))
|
||||
{
|
||||
$request->validate([
|
||||
'ipaddress-search' => ['required', 'ip']
|
||||
]);
|
||||
|
||||
$result = UserIp::where('ipAddress', $request->get('ipaddress-search'))
|
||||
->join('users', 'users.id', '=', 'user_ips.userId')
|
||||
->orderBy('users.id', 'desc');
|
||||
|
||||
return view('web.admin.usersearch')->with('users', $result->get())->with('isIpSearch', true);
|
||||
}
|
||||
return view('web.admin.usersearch')->with('users', $users);
|
||||
}
|
||||
|
||||
function userSearchQueryUserName(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'username' => ['required', 'string']
|
||||
]);
|
||||
|
||||
return view('web.admin.usersearch')->with('error', 'Input validation failed.');
|
||||
$users = User::where('username', 'like', '%' . $request->get('username') . '%')
|
||||
->paginate(25)
|
||||
->appends($request->all());
|
||||
|
||||
return view('web.admin.usersearch')->with('users', $users);
|
||||
}
|
||||
|
||||
function userSearchQueryIpAddress(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'ipaddress' => ['required', 'ip']
|
||||
]);
|
||||
|
||||
$users = UserIp::where('ipAddress', $request->get('ipaddress'))
|
||||
->join('users', 'users.id', '=', 'user_ips.userId')
|
||||
->orderBy('users.id', 'desc')
|
||||
->paginate(25)
|
||||
->appends($request->all());
|
||||
|
||||
return view('web.admin.usersearch')->with('users', $users)->with('isIpSearch', true);
|
||||
}
|
||||
|
||||
// GET admin.userlookup
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Web;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class AvatarController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('web.avatar.editor');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Web;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MoneyController extends Controller
|
||||
{
|
||||
public function transactions()
|
||||
{
|
||||
return view('web.money.transactions');
|
||||
}
|
||||
}
|
||||
|
|
@ -92,6 +92,16 @@ class ArbiterRender implements ShouldQueue
|
|||
];
|
||||
|
||||
switch($this->type) {
|
||||
case 'Bust':
|
||||
$this->type = 'Closeup';
|
||||
array_push($arguments, false); // Quadratic
|
||||
array_push($arguments, 30); // Base Hat Zoom
|
||||
array_push($arguments, 100); // Max Hat Zoom
|
||||
array_push($arguments, 0); // Camera Offset X
|
||||
array_push($arguments, 0); // Camera Offset Y
|
||||
case 'Avatar':
|
||||
$arguments[0] = url('test', ['id' => $this->assetId]); // TODO: this
|
||||
break;
|
||||
case 'Head':
|
||||
case 'Shirt':
|
||||
case 'Pants':
|
||||
|
|
@ -185,7 +195,10 @@ class ArbiterRender implements ShouldQueue
|
|||
->resize($arguments[2]/4, $arguments[3]/4)
|
||||
->toString();
|
||||
|
||||
$this->tracker->targetObj->set2DHash(CdnHelper::SaveContentB64(base64_encode($image), 'image/png'));
|
||||
if($this->type == 'Closeup')
|
||||
$this->tracker->targetObj->setBustHash(CdnHelper::SaveContentB64(base64_encode($image), 'image/png'));
|
||||
else
|
||||
$this->tracker->targetObj->set2DHash(CdnHelper::SaveContentB64(base64_encode($image), 'image/png'));
|
||||
}
|
||||
|
||||
$this->tracker->delete();
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class RenderTracker extends Model
|
|||
|
||||
public function targetObj()
|
||||
{
|
||||
if($this->type == 'user2d' || $this->type == 'user3d')
|
||||
if($this->type == 'user2d' || $this->type == 'user2dbust' || $this->type == 'user3d')
|
||||
return $this->belongsTo(User::class, 'target');
|
||||
elseif($this->type == 'asset2d' || $this->type == 'asset3d')
|
||||
return $this->belongsTo(Asset::class, 'target');
|
||||
|
|
|
|||
|
|
@ -9,6 +9,16 @@ class Transaction extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
|
|
@ -23,9 +33,25 @@ class Transaction extends Model
|
|||
'seller_id'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function seller()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'seller_id');
|
||||
}
|
||||
|
||||
public function asset()
|
||||
{
|
||||
return $this->belongsTo(Asset::class, 'asset_id');
|
||||
}
|
||||
|
||||
protected static function createPurchase($transaction)
|
||||
{
|
||||
return self::create(array_merge($transaction, [
|
||||
'delta' => -$transaction->delta,
|
||||
'transaction_type_id' => TransactionType::where('name', 'Purchases')->first()->id
|
||||
]));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,4 +8,9 @@ use Illuminate\Database\Eloquent\Model;
|
|||
class TransactionType extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public static function IDFromType($type)
|
||||
{
|
||||
return self::where('name', $type)->first()->id;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
use App\Notifications\ResetPasswordNotification;
|
||||
|
|
@ -166,6 +167,59 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
$this->save();
|
||||
}
|
||||
|
||||
public function getImageUrl()
|
||||
{
|
||||
$renderId = $this->id;
|
||||
|
||||
$thumbnail = Http::get(route('thumbnails.v1.user', ['id' => $renderId, 'position' => 'full', 'type' => '2d']));
|
||||
if($thumbnail->json('status') == 'loading')
|
||||
return '/images/busy/user.png';
|
||||
|
||||
return $thumbnail->json('data');
|
||||
}
|
||||
|
||||
public function getHeadshotImageUrl()
|
||||
{
|
||||
$renderId = $this->id;
|
||||
|
||||
$thumbnail = Http::get(route('thumbnails.v1.user', ['id' => $renderId, 'position' => 'bust', 'type' => '2d']));
|
||||
if($thumbnail->json('status') == 'loading')
|
||||
return '/images/busy/user.png';
|
||||
|
||||
return $thumbnail->json('data');
|
||||
}
|
||||
|
||||
public function set2DHash($hash)
|
||||
{
|
||||
$this->thumbnail2DHash = $hash;
|
||||
$this->timestamps = false;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function setBustHash($hash)
|
||||
{
|
||||
$this->thumbnailBustHash = $hash;
|
||||
$this->timestamps = false;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function set3DHash($hash)
|
||||
{
|
||||
$this->thumbnail3DHash = $hash;
|
||||
$this->timestamps = false;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function userToJson()
|
||||
{
|
||||
return [
|
||||
'type' => 'User',
|
||||
'name' => $this->username,
|
||||
'thumbnail' => $this->getHeadshotImageUrl(),
|
||||
'url' => $this->getProfileUrl()
|
||||
];
|
||||
}
|
||||
|
||||
public function _hasRolesetInternal($roleName)
|
||||
{
|
||||
$roleset = Roleset::where('Name', $roleName)->first();
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ class Asset extends Model
|
|||
|
||||
$thumbnail = Http::get(route('thumbnails.v1.asset', ['id' => $renderId, 'type' => '2d']));
|
||||
if($thumbnail->json('status') == 'loading')
|
||||
return ($this->assetTypeId == 9 ? 'https://virtubrick.local/images/busy/game.png' : 'https://virtubrick.local/images/busy/asset.png');
|
||||
return ($this->assetTypeId == 9 ? '/images/busy/game.png' : '/images/busy/asset.png');
|
||||
|
||||
return $thumbnail->json('data');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ return new class extends Migration
|
|||
$table->unsignedBigInteger('tokens')->default(0);
|
||||
$table->dateTime('next_reward')->useCurrent();
|
||||
|
||||
$table->string('thumbnailBustHash')->nullable();
|
||||
$table->string('thumbnail2DHash')->nullable();
|
||||
$table->string('thumbnail3DHash')->nullable();
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -127,6 +127,7 @@ class ShopCategoryButton extends Component {
|
|||
let categoryAssetTypeIds = this.props.getCategoryAssetTypeIds(categoryName);
|
||||
|
||||
switch(typeof(categoryAssetTypeIds.assetTypeId)) {
|
||||
case 'string':
|
||||
case 'number':
|
||||
assetTypes[categoryAssetTypeIds.assetTypeId] = true;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import { Component, createRef } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import { buildGenericApiUrl } from '../util/HTTP.js';
|
||||
import Loader from './Loader';
|
||||
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
class SummaryTab extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loaded: false,
|
||||
filters: [
|
||||
{label: 'Past Day', tag: 'pastday', ref: createRef()},
|
||||
{label: 'Past Week', tag: 'pastweek', ref: createRef()},
|
||||
{label: 'Past Month', tag: 'pastmonth', ref: createRef()},
|
||||
{label: 'Past Year', tag: 'pastyear', ref: createRef()},
|
||||
{label: 'All Time', tag: 'all', ref: createRef()}
|
||||
],
|
||||
items: [],
|
||||
total: null
|
||||
};
|
||||
|
||||
this.categoryDropdown = createRef();
|
||||
|
||||
this.setTag = this.setTag.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setTag('pastday');
|
||||
}
|
||||
|
||||
setTag(tag) {
|
||||
this.setState({ loaded: false });
|
||||
|
||||
let selectedTag = this.state.filters.find(obj => {
|
||||
return obj.tag === tag
|
||||
});
|
||||
this.categoryDropdown.current.innerText = selectedTag.label;
|
||||
|
||||
this.state.filters.map(({ label, tag, ref }) => {
|
||||
if(ref.current.innerText == selectedTag.label)
|
||||
ref.current.classList.add('active');
|
||||
else
|
||||
ref.current.classList.remove('active');
|
||||
});
|
||||
|
||||
axios.get(buildGenericApiUrl('api', `shop/v1/user-summary?filter=${tag}`))
|
||||
.then(res => {
|
||||
const response = res.data;
|
||||
this.setState({ items: response.columns, total: response.total, loaded: true });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<p className="d-inline-block me-2">Time Period:</p>
|
||||
<button className="btn btn-secondary dropdown-toggle" type="button" disabled={ !this.state.loaded } ref={ this.categoryDropdown } data-bs-toggle="dropdown" aria-expanded="false">
|
||||
...
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
{
|
||||
this.state.filters.map(({ label, tag, ref }) =>
|
||||
<li><button className="dropdown-item" ref={ ref } onClick={ () => this.setTag(tag) }>{ label }</button></li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
{
|
||||
this.state.loaded
|
||||
?
|
||||
<>
|
||||
<p className="virtubrick-tokens">Tokens</p>
|
||||
<table className="table virtubrick-table mt-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Categories</th>
|
||||
<th scope="col">Credit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
this.state.items.map(({ name, total }, index) =>
|
||||
<tr className="align-middle">
|
||||
<th scope="col">{ name }</th>
|
||||
<th scope="col">{ total != 0 ? <span className="virtubrick-tokens">{ total }</span> : null }</th>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<p className="text-right">Total <span className="virtubrick-tokens">{ this.state.total }</span></p>
|
||||
</>
|
||||
:
|
||||
<div className="d-flex">
|
||||
<Loader />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class TransactionsTab extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loaded: false,
|
||||
loadingMore: false,
|
||||
filters: [
|
||||
{label: 'Purchases', tag: 'purchases', ref: createRef()},
|
||||
{label: 'Sales', tag: 'sales', ref: createRef()},
|
||||
{label: 'Commissions', tag: 'commissions', ref: createRef()},
|
||||
{label: 'Group Payouts', tag: 'grouppayouts', ref: createRef()}
|
||||
],
|
||||
items: []
|
||||
};
|
||||
|
||||
this.categoryDropdown = createRef();
|
||||
|
||||
this.setTag = this.setTag.bind(this);
|
||||
this.loadMore = this.loadMore.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
window.addEventListener('scroll', this.loadMore);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('scroll', this.loadMore);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setTag('purchases');
|
||||
}
|
||||
|
||||
setTag(tag) {
|
||||
this.setState({ loaded: false });
|
||||
|
||||
let selectedTag = this.state.filters.find(obj => {
|
||||
return obj.tag === tag
|
||||
});
|
||||
this.categoryDropdown.current.innerText = selectedTag.label;
|
||||
|
||||
this.state.filters.map(({ label, tag, ref }) => {
|
||||
if(ref.current.innerText == selectedTag.label)
|
||||
ref.current.classList.add('active');
|
||||
else
|
||||
ref.current.classList.remove('active');
|
||||
});
|
||||
|
||||
this.nextCursor = null;
|
||||
|
||||
axios.get(buildGenericApiUrl('api', `shop/v1/user-transactions?filter=${tag}`))
|
||||
.then(res => {
|
||||
const response = res.data;
|
||||
|
||||
this.nextCursor = response.next_cursor;
|
||||
this.setState({ items: response.data, filter: tag, loaded: true });
|
||||
});
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
// XlXi: Taking the height of the footer into account.
|
||||
if (window.innerHeight + document.documentElement.scrollTop >= document.scrollingElement.scrollHeight-200) {
|
||||
if (!!(this.nextCursor) && !this.state.loading && !this.state.loadingMore) {
|
||||
this.setState({ loadingMore: true });
|
||||
|
||||
axios.get(buildGenericApiUrl('api', `shop/v1/user-transactions?filter=${this.state.filter}&cursor=${this.nextCursor}`))
|
||||
.then(res => {
|
||||
const response = res.data;
|
||||
|
||||
this.nextCursor = response.next_cursor;
|
||||
this.setState({ items: this.state.items.concat(response.data), loadingMore: false });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<p className="d-inline-block me-2">Currently Selected:</p>
|
||||
<button className="btn btn-secondary dropdown-toggle" type="button" disabled={ !this.state.loaded } ref={ this.categoryDropdown } data-bs-toggle="dropdown" aria-expanded="false">
|
||||
...
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
{
|
||||
this.state.filters.map(({ label, tag, ref }) =>
|
||||
<li><button className="dropdown-item" ref={ ref } onClick={ () => this.setTag(tag) }>{ label }</button></li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
{
|
||||
this.state.loaded
|
||||
?
|
||||
<>
|
||||
<p className="virtubrick-tokens">Tokens</p>
|
||||
<table className="table virtubrick-table mt-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Date</th>
|
||||
<th scope="col">Member</th>
|
||||
<th scope="col">Description</th>
|
||||
<th scope="col">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
this.state.items.map(({ date, member, description, amount, item }, index) =>
|
||||
<tr className="align-middle">
|
||||
<th scope="col">{ date }</th>
|
||||
<th scope="col"><a href={ member.url } className="text-decoration-none">{ member.name }</a></th>
|
||||
<th scope="col">{ description }{ item != null ? <a href={ item.url } className="text-decoration-none">{ item.name }</a> : null }</th>
|
||||
<th scope="col"><p className="virtubrick-tokens">{ amount }</p></th>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
{
|
||||
this.state.loadingMore
|
||||
?
|
||||
<div className="d-flex">
|
||||
<Loader />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</>
|
||||
:
|
||||
<div className="d-flex">
|
||||
<Loader />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Transactions extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loaded: false,
|
||||
tabs: [
|
||||
{label: 'Summary', name: 'summary', ref: createRef()},
|
||||
{label: 'My Transactions', name: 'transactions', ref: createRef()}
|
||||
]
|
||||
};
|
||||
|
||||
this.tabIndex = 0;
|
||||
|
||||
this.setCurrentTab = this.setCurrentTab.bind(this);
|
||||
this.setTab = this.setTab.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const params = new Proxy(new URLSearchParams(window.location.search), {
|
||||
get: (searchParams, prop) => searchParams.get(prop),
|
||||
});
|
||||
let queryTab = this.state.tabs.find(obj => {
|
||||
if(!params.tab)
|
||||
return false;
|
||||
|
||||
return obj.name === params.tab.toLowerCase();
|
||||
});
|
||||
|
||||
if(queryTab)
|
||||
this.setTab(queryTab.name);
|
||||
else
|
||||
this.setTab('summary');
|
||||
}
|
||||
|
||||
setCurrentTab(instance)
|
||||
{
|
||||
this.currentTab = instance;
|
||||
this.setState({loaded: true});
|
||||
}
|
||||
|
||||
setTab(tabType) {
|
||||
this.setState({loaded: false});
|
||||
this.tabIndex += 1;
|
||||
|
||||
switch(tabType)
|
||||
{
|
||||
case 'summary':
|
||||
this.setCurrentTab(<SummaryTab key={this.tabIndex} />);
|
||||
break;
|
||||
case 'transactions':
|
||||
this.setCurrentTab(<TransactionsTab key={this.tabIndex} />);
|
||||
break;
|
||||
}
|
||||
|
||||
this.state.tabs.map(({ name, ref }) => {
|
||||
if(name == tabType)
|
||||
ref.current.classList.add('active');
|
||||
else
|
||||
ref.current.classList.remove('active');
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<ul className="nav nav-tabs">
|
||||
{
|
||||
this.state.tabs.map(({ label, name, ref }) =>
|
||||
<li className="nav-item">
|
||||
<button className="nav-link" onClick={ () => this.setTab(name) } ref={ ref }>{ label }</button>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
<div className="card p-2">
|
||||
{ this.state.loaded ? this.currentTab : <Loader /> }
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Transactions;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
|
||||
import Transactions from '../components/Transactions';
|
||||
|
||||
const transactionsId = 'vb-transactions';
|
||||
|
||||
$(document).ready(function() {
|
||||
if (document.getElementById(transactionsId)) {
|
||||
render(<Transactions />, document.getElementById(transactionsId));
|
||||
}
|
||||
});
|
||||
|
|
@ -9,21 +9,20 @@
|
|||
@if(!isset($nolabel) || !$nolabel)
|
||||
<label for="vb-{{ $id }}-search" class="form-label">{{ $definition }}</label>
|
||||
@endif
|
||||
<form method="POST" action="{{ route('admin.usersearch') }}" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<form method="GET" action="{{ route('admin.usersearch') }}">
|
||||
<div class="input-group mb-3">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="{{ $definition }} Here"
|
||||
aria-label="{{ $definition }} Here"
|
||||
name="{{ $id }}-search" id="vb-{{ $id }}-search"
|
||||
name="{{ $id }}" id="vb-{{ $id }}-search"
|
||||
aria-describedby="vb-{{ $id }}-search-btn"
|
||||
@if(isset($value))
|
||||
value="{{ $value }}"
|
||||
@endif
|
||||
>
|
||||
<button type="submit" class="btn btn-primary" type="button" name="{{ $id }}-button" id="vb-{{ $id }}-search-btn">Search</button>
|
||||
<button type="submit" class="btn btn-primary" type="button" id="vb-{{ $id }}-search-btn">Search</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -24,9 +24,8 @@
|
|||
@endphp
|
||||
|
||||
<span class="d-flex align-items-center">
|
||||
{{-- TODO: XlXi: User headshots --}}
|
||||
<img
|
||||
src="{{ asset('images/testing/headshot.png') }}"
|
||||
src="{{ $user->getHeadshotImageUrl() }}"
|
||||
@class($classes)
|
||||
width="{{ $size }}"
|
||||
height="{{ $size }}"
|
||||
|
|
|
|||
|
|
@ -166,19 +166,23 @@
|
|||
</li>
|
||||
</ul>
|
||||
<div class="d-md-flex">
|
||||
<p class="my-auto me-2 virtubrick-tokens" data-bs-toggle="tooltip" data-bs-placement="bottom" title="You have {{ number_format(Auth::user()->tokens) }} tokens. Your next reward is in {{ Auth::user()->next_reward->diffForHumans(['syntax' => Carbon\CarbonInterface::DIFF_ABSOLUTE]) }}.">
|
||||
{{ \App\Helpers\NumberHelper::Abbreviate(Auth::user()->tokens) }}
|
||||
</p>
|
||||
<a href="{{ route('user.transactions') }}" class="my-auto me-2 text-decoration-none">
|
||||
<span>
|
||||
<p class="virtubrick-tokens" href="" data-bs-toggle="tooltip" data-bs-placement="bottom" title="You have {{ number_format(Auth::user()->tokens) }} tokens. Your next reward is in {{ Auth::user()->next_reward->diffForHumans(['syntax' => Carbon\CarbonInterface::DIFF_ABSOLUTE]) }}.">
|
||||
{{ \App\Helpers\NumberHelper::Abbreviate(Auth::user()->tokens) }}
|
||||
</p>
|
||||
</span>
|
||||
</a>
|
||||
<div class="dropdown">
|
||||
<a class="nav-link dropdown-toggle virtubrick-user-dropdown px-0 px-md-3" href="#" id="virtubrick-user-dropdown" role="button" data-bs-toggle="dropdown" area-expanded="false">
|
||||
<x-user-circle :user="Auth::user()" :statusIndicator=false />
|
||||
</a>
|
||||
<ul class="dropdown-menu virtubrick-user-dropdown" area-labelledby="virtubrick-user-dropdown">
|
||||
<li><a class="dropdown-item" href="{{ Auth::user()->getProfileUrl() }}">Profile</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url('/todo123') }}">Character</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url('/my/settings') }}">Settings</a></li>
|
||||
<li><a class="dropdown-item" href="{{ route('user.avatarEditor') }}">Character</a></li>
|
||||
<li><a class="dropdown-item" href="{{ route('user.settings') }}">Settings</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="{{ url('/logout') }}">Sign out</a></li>
|
||||
<li><a class="dropdown-item" href="{{ route('auth.logout') }}">Sign out</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
@if ($paginator->hasPages())
|
||||
<ul class="list-inline mx-auto mt-3">
|
||||
<li class="list-inline-item">
|
||||
<a @class(['btn', 'btn-secondary', 'disabled' => $paginator->onFirstPage()]) {!! !$paginator->onFirstPage() ? 'href="' . $paginator->previousPageUrl() . '"' : '' !!}><i class="fa-solid fa-angle-left"></i></a>
|
||||
</li>
|
||||
<li class="list-inline-item virtubrick-paginator">
|
||||
<span>Page {{ $paginator->currentPage() }} of {{ $paginator->lastPage() }}</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a @class(['btn', 'btn-secondary', 'disabled' => !$paginator->hasMorePages()]) {!! $paginator->hasMorePages() ? 'href="' . $paginator->nextPageUrl() . '"' : '' !!}><i class="fa-solid fa-angle-right"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
@endif
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
'virtubrick-error-popup',
|
||||
'mt-3' => !$isProtected
|
||||
])>
|
||||
This user is a {{ $powerType }}.
|
||||
This user is a(n) {{ $powerType }}.
|
||||
</div>
|
||||
@endif
|
||||
<x-admin.user-admin-label label="Username">{{ $user->username }}</x-admin.user-admin-label>
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
<x-admin.user-admin-label label="Current Location">Website <b>TODO</b></x-admin.user-admin-label>
|
||||
<div class="row py-2 border-top">
|
||||
<div class="col-6">
|
||||
<img src="{{ asset('/images/testing/avatar.png') }}" width="200" height="200" class="img-fluid vb-charimg" />
|
||||
<img src="{{ $user->getImageUrl() }}" width="200" height="200" class="img-fluid vb-charimg" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<a href="{{ $user->getProfileUrl() }}" class="text-decoration-none">User Homepage</a><br/>
|
||||
|
|
@ -85,6 +85,7 @@
|
|||
<table class="table virtubrick-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Action</th>
|
||||
<th scope="col">Moderator</th>
|
||||
|
|
@ -96,7 +97,12 @@
|
|||
<tbody>
|
||||
@foreach($user->punishments as $punishment)
|
||||
<tr>
|
||||
<th scope="col">{{ $punishment->id }}</th>
|
||||
<th scope="col">
|
||||
<button class="btn btn-sm p-0 px-1 text-decoration-none" type="button" data-bs-toggle="collapse" data-bs-target="#punishment-collapse-{{ $punishment->id }}" aria-expanded="false" aria-controls="punishment-collapse-{{ $punishment->id }}">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
</button>
|
||||
</th>
|
||||
<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">
|
||||
|
|
@ -107,6 +113,27 @@
|
|||
<th scope="col">{{ $punishment->expirationStr() }}</th>
|
||||
<th scope="col">{{ $punishment->active ? 'No' : 'Yes' }}</th>
|
||||
</tr>
|
||||
<tr class="collapse" id="punishment-collapse-{{ $punishment->id }}">
|
||||
<td colspan="7" class="bg-secondary">
|
||||
<div class="mx-2">
|
||||
<p><b>Note to User:</b> {{ $punishment->user_note }}</p>
|
||||
@if($punishment->context->count() > 0)
|
||||
<p><b>Abuses:</b></p>
|
||||
@endif
|
||||
@foreach($punishment->context as $context)
|
||||
<div class="card bg-secondary p-2 mb-2 border-1">
|
||||
<p><b>Reason:</b> {{ $context->user_note }} (<a href="#" class="text-decoration-none">TODO: audit</a>)</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>
|
||||
@endforeach
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@
|
|||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
{{ $users->links('pagination.virtubrick') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Avatar Editor')
|
||||
|
||||
@section('content')
|
||||
<div class="container my-2">
|
||||
<h4>Avatar Editor</h4>
|
||||
<div class="row mt-2">
|
||||
<div class="col-3">
|
||||
<div class="card text-center" id="vb-character-thumbnail">
|
||||
<img src="{{ Auth::user()->getImageUrl() }}" class="img-fluid vb-charimg" />
|
||||
</div>
|
||||
|
||||
<h4 class="mt-3">Colors</h4>
|
||||
<div class="card p-2" id="vb-character-colors">
|
||||
<x-loader />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<ul class="nav nav-tabs" id="vb-character-tabs">
|
||||
<li class="nav-item">
|
||||
<button class="nav-link active disabled" disabled>Wardrobe</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button class="nav-link disabled" disabled>Outfits</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="card p-2" id="vb-character-editor">
|
||||
<x-loader />
|
||||
</div>
|
||||
|
||||
<h4 class="mt-3">Currently Wearing</h4>
|
||||
<div class="card p-2" id="vb-character-wearing">
|
||||
<x-loader />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Todo:</p>
|
||||
<ul>
|
||||
<li>Character Preview</li>
|
||||
<li>3d Character Preview</li>
|
||||
<li>Redraw button</li>
|
||||
<li>Section for changing body colors</li>
|
||||
<li>Section for wearing items</li>
|
||||
<li>Section for taking off worn items</li>
|
||||
</ul>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
<div class="col-md-3">
|
||||
<h4>Hello, {{ Auth::user()->username }}!</h4>
|
||||
<div class="card text-center">
|
||||
<img src="{{ asset('/images/testing/avatar.png') }}" class="img-fluid vb-charimg" />
|
||||
<img src="{{ Auth::user()->getImageUrl() }}" class="img-fluid vb-charimg" />
|
||||
</div>
|
||||
|
||||
<h4 class="mt-3">Blog</h4>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Transactions')
|
||||
|
||||
@section('page-specific')
|
||||
<script src="{{ mix('js/Transactions.js') }}"></script>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="container my-2">
|
||||
<h4 class="mb-2">Transactions</h4>
|
||||
<div id="vb-transactions">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<button class="nav-link active disabled" disabled>Summary</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button class="nav-link disabled" disabled>My Transactions</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="card p-2">
|
||||
<x-loader />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
<h3 class="mb-0">{{ $asset->name }}</h3>
|
||||
<p>
|
||||
By <a class="text-decoration-none fw-normal" href="{{ $asset->user->getProfileUrl() }}">{{ $asset->user->username }}</a>
|
||||
@if(Auth::user()->hasAsset($asset->id))
|
||||
@if(Auth::user() && Auth::user()->hasAsset($asset->id))
|
||||
<span>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
<div class="card p-2">
|
||||
<div class="d-flex">
|
||||
<div class="pe-3">
|
||||
<img class="img-fluid border virtubrick-user-circle m-1" src="{{ asset('/images/testing/headshot.png') }}" alt="User avatar of {{ $user->username }}" width="120px" />
|
||||
<img class="img-fluid border virtubrick-user-circle m-1" src="{{ $user->getHeadshotImageUrl() }}" alt="User avatar of {{ $user->username }}" width="120px" />
|
||||
</div>
|
||||
<div class="flex-fill d-flex flex-column p-2">
|
||||
{{-- TODO: XlXi: Advanced presence --}}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ Route::group(['as' => 'shop.', 'prefix' => 'shop'], function() {
|
|||
Route::group(['as' => 'v1.', 'prefix' => 'v1'], function() {
|
||||
Route::get('/list-json', 'ShopController@listJson')->name('list');
|
||||
Route::post('/purchase/{asset}', 'ShopController@purchase')->name('purchase');
|
||||
|
||||
Route::get('/user-summary', 'MoneyController@userSummary')->name('summary');
|
||||
Route::get('/user-transactions', 'MoneyController@userTransactions')->name('transactions');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,14 @@ Route::group(['as' => 'games.', 'prefix' => 'games'], function() {
|
|||
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::group(['as' => 'user.', 'prefix' => 'my'], function() {
|
||||
Route::get('/settings', 'SettingsController@index')->name('index');
|
||||
Route::get('/settings', 'SettingsController@index')->name('settings');
|
||||
Route::get('/avatar', 'AvatarController@index')->name('avatarEditor');
|
||||
Route::get('/transactions', 'MoneyController@transactions')->name('transactions');
|
||||
});
|
||||
|
||||
Route::group(['as' => 'punishment.', 'prefix' => 'membership'], function() {
|
||||
Route::get('/not-approved', 'ModerationController@notice')->name('notice');
|
||||
Route::post('/not-approved', 'ModerationController@reactivate')->name('reactivate');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -50,7 +57,6 @@ Route::group(['as' => 'admin.', 'prefix' => 'admin'], function() {
|
|||
Route::group(['prefix' => 'users'], function() {
|
||||
Route::get('/useradmin', 'AdminController@userAdmin')->name('useradmin');
|
||||
Route::get('/find', 'AdminController@userSearch')->name('usersearch');
|
||||
Route::post('/find', 'AdminController@userSearchQuery')->name('usersearchquery');
|
||||
Route::get('/userlookuptool', 'AdminController@userLookup')->name('userlookup');
|
||||
Route::post('/userlookuptool', 'AdminController@userLookupQuery')->name('userlookupquery');
|
||||
});
|
||||
|
|
@ -118,13 +124,6 @@ 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() {
|
||||
Route::get('/asset', 'ClientController@asset')->name('asset');
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
local characterAppearanceUrl, baseUrl, fileExtension, x, y = ...
|
||||
local characterAppearanceUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ContentProvider"):SetThreadPool(16)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
local baseUrl, characterAppearanceUrl, fileExtension, x, y, quadratic, baseHatZoom, maxHatZoom, cameraOffsetX, cameraOffsetY = ...
|
||||
local characterAppearanceUrl, fileExtension, x, y, baseUrl, quadratic, baseHatZoom, maxHatZoom, cameraOffsetX, cameraOffsetY = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ContentProvider"):SetThreadPool(16)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ mix.js('resources/js/app.js', 'public/js')
|
|||
.js('resources/js/pages/Dashboard.js', 'public/js')
|
||||
.js('resources/js/pages/Shop.js', 'public/js')
|
||||
.js('resources/js/pages/Games.js', 'public/js')
|
||||
.js('resources/js/pages/Transactions.js', 'public/js')
|
||||
.js('resources/js/pages/Item.js', 'public/js')
|
||||
.js('resources/js/pages/Place.js', 'public/js')
|
||||
.js('resources/js/pages/AppDeployer.js', 'public/js/adm')
|
||||
|
|
|
|||
Loading…
Reference in New Issue