mirror of
https://github.com/sp-tarkov/forge.git
synced 2025-02-13 04:30:41 -05:00
Merge remote-tracking branch 'upstream/develop' into fix/#39
This commit is contained in:
commit
5aaf31ab84
@ -11,8 +11,16 @@ class UserController extends Controller
|
|||||||
{
|
{
|
||||||
use AuthorizesRequests;
|
use AuthorizesRequests;
|
||||||
|
|
||||||
public function show(Request $request, User $user, string $username): View
|
public function show(Request $request, int $userId, string $username): View
|
||||||
{
|
{
|
||||||
|
$user = User::whereId($userId)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
$mods = $user->mods()
|
||||||
|
->orderByDesc('created_at')
|
||||||
|
->paginate(10)
|
||||||
|
->fragment('mods');
|
||||||
|
|
||||||
if ($user->slug() !== $username) {
|
if ($user->slug() !== $username) {
|
||||||
abort(404);
|
abort(404);
|
||||||
}
|
}
|
||||||
@ -21,6 +29,6 @@ class UserController extends Controller
|
|||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('user.show', compact('user'));
|
return view('user.show', compact('user', 'mods'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Http\Filters\V1;
|
namespace App\Http\Filters\V1;
|
||||||
|
|
||||||
use App\Models\Mod;
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends QueryFilter<Mod>
|
|
||||||
*/
|
|
||||||
class ModFilter extends QueryFilter
|
class ModFilter extends QueryFilter
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -2,12 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Http\Filters\V1;
|
namespace App\Http\Filters\V1;
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends QueryFilter<User>
|
|
||||||
*/
|
|
||||||
class UserFilter extends QueryFilter
|
class UserFilter extends QueryFilter
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -49,6 +49,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
|
|
||||||
// Begin to import the data into the permanent local database tables.
|
// Begin to import the data into the permanent local database tables.
|
||||||
$this->importUsers();
|
$this->importUsers();
|
||||||
|
$this->importUserFollows();
|
||||||
$this->importLicenses();
|
$this->importLicenses();
|
||||||
$this->importSptVersions();
|
$this->importSptVersions();
|
||||||
$this->importMods();
|
$this->importMods();
|
||||||
@ -608,6 +609,37 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function importUserFollows(): void
|
||||||
|
{
|
||||||
|
$followsGroupedByFollower = [];
|
||||||
|
|
||||||
|
DB::connection('mysql_hub')
|
||||||
|
->table('wcf1_user_follow')
|
||||||
|
->select(['followID', 'userID', 'followUserID', 'time'])
|
||||||
|
->chunkById(100, function (Collection $follows) use (&$followsGroupedByFollower) {
|
||||||
|
foreach ($follows as $follow) {
|
||||||
|
$followerId = User::whereHubId($follow->userID)->value('id');
|
||||||
|
$followingId = User::whereHubId($follow->followUserID)->value('id');
|
||||||
|
|
||||||
|
if (! $followerId || ! $followingId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$followsGroupedByFollower[$followerId][$followingId] = [
|
||||||
|
'created_at' => Carbon::parse($follow->time, 'UTC'),
|
||||||
|
'updated_at' => Carbon::parse($follow->time, 'UTC'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}, 'followID');
|
||||||
|
|
||||||
|
foreach ($followsGroupedByFollower as $followerId => $followings) {
|
||||||
|
$user = User::find($followerId);
|
||||||
|
if ($user) {
|
||||||
|
$user->following()->sync($followings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import the licenses from the Hub database to the local database.
|
* Import the licenses from the Hub database to the local database.
|
||||||
*/
|
*/
|
||||||
|
@ -7,6 +7,7 @@ use App\Models\User;
|
|||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class GlobalSearch extends Component
|
class GlobalSearch extends Component
|
||||||
@ -17,20 +18,26 @@ class GlobalSearch extends Component
|
|||||||
public string $query = '';
|
public string $query = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to show the search result dropdown.
|
* The search results.
|
||||||
*/
|
*/
|
||||||
public bool $showDropdown = false;
|
#[Locked]
|
||||||
|
public array $result = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to show the "no results found" message.
|
* The total number of search results.
|
||||||
*/
|
*/
|
||||||
public bool $noResults = false;
|
#[Locked]
|
||||||
|
public int $count = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the component.
|
||||||
|
*/
|
||||||
public function render(): View
|
public function render(): View
|
||||||
{
|
{
|
||||||
return view('livewire.global-search', [
|
$this->result = $this->executeSearch($this->query);
|
||||||
'results' => $this->executeSearch($this->query),
|
$this->count = $this->countTotalResults($this->result);
|
||||||
]);
|
|
||||||
|
return view('livewire.global-search');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,19 +46,15 @@ class GlobalSearch extends Component
|
|||||||
protected function executeSearch(string $query): array
|
protected function executeSearch(string $query): array
|
||||||
{
|
{
|
||||||
$query = Str::trim($query);
|
$query = Str::trim($query);
|
||||||
$results = ['data' => [], 'total' => 0];
|
|
||||||
|
|
||||||
if (Str::length($query) > 0) {
|
if (Str::length($query) > 0) {
|
||||||
$results['data'] = [
|
return [
|
||||||
'user' => $this->fetchUserResults($query),
|
'user' => $this->fetchUserResults($query),
|
||||||
'mod' => $this->fetchModResults($query),
|
'mod' => $this->fetchModResults($query),
|
||||||
];
|
];
|
||||||
$results['total'] = $this->countTotalResults($results['data']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->noResults = $results['total'] === 0;
|
return [];
|
||||||
|
|
||||||
return $results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,10 +62,7 @@ class GlobalSearch extends Component
|
|||||||
*/
|
*/
|
||||||
protected function fetchUserResults(string $query): Collection
|
protected function fetchUserResults(string $query): Collection
|
||||||
{
|
{
|
||||||
/** @var array<int, array<string, mixed>> $userHits */
|
return collect(User::search($query)->raw()['hits']);
|
||||||
$userHits = User::search($query)->raw()['hits'];
|
|
||||||
|
|
||||||
return collect($userHits);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,10 +70,7 @@ class GlobalSearch extends Component
|
|||||||
*/
|
*/
|
||||||
protected function fetchModResults(string $query): Collection
|
protected function fetchModResults(string $query): Collection
|
||||||
{
|
{
|
||||||
/** @var array<int, array<string, mixed>> $modHits */
|
return collect(Mod::search($query)->raw()['hits']);
|
||||||
$modHits = Mod::search($query)->raw()['hits'];
|
|
||||||
|
|
||||||
return collect($modHits);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
52
app/Livewire/User/FollowButtons.php
Normal file
52
app/Livewire/User/FollowButtons.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\User;
|
||||||
|
|
||||||
|
use Illuminate\View\View;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class FollowButtons extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The ID of the user whose profile is being viewed.
|
||||||
|
*/
|
||||||
|
#[Locked]
|
||||||
|
public int $profileUserId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the authenticated user is currently following the profile user.
|
||||||
|
*/
|
||||||
|
#[Locked]
|
||||||
|
public bool $isFollowing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to follow a user.
|
||||||
|
*/
|
||||||
|
public function follow(): void
|
||||||
|
{
|
||||||
|
auth()->user()->follow($this->profileUserId);
|
||||||
|
$this->isFollowing = true;
|
||||||
|
|
||||||
|
$this->dispatch('user-follow-change');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to unfollow a user.
|
||||||
|
*/
|
||||||
|
public function unfollow(): void
|
||||||
|
{
|
||||||
|
auth()->user()->unfollow($this->profileUserId);
|
||||||
|
$this->isFollowing = false;
|
||||||
|
|
||||||
|
$this->dispatch('user-follow-change');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('livewire.user.follow-buttons');
|
||||||
|
}
|
||||||
|
}
|
152
app/Livewire/User/FollowCard.php
Normal file
152
app/Livewire/User/FollowCard.php
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\User;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Attributes\On;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class FollowCard extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The ID of the user whose profile is being viewed.
|
||||||
|
*/
|
||||||
|
#[Locked]
|
||||||
|
public int $profileUserId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of user follow relationship to display.
|
||||||
|
* Currently, either "followers" or "following".
|
||||||
|
*/
|
||||||
|
#[Locked]
|
||||||
|
public string $relationship;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The title of the card.
|
||||||
|
*/
|
||||||
|
public string $title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message to display when there are no results.
|
||||||
|
*/
|
||||||
|
public string $emptyMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The title of the dialog.
|
||||||
|
*/
|
||||||
|
public string $dialogTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The users to display in the card.
|
||||||
|
*
|
||||||
|
* @var Collection<User>
|
||||||
|
*/
|
||||||
|
#[Locked]
|
||||||
|
public Collection $followUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of users to display on the card.
|
||||||
|
*/
|
||||||
|
#[Locked]
|
||||||
|
public int $limit = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show all users in a model dialog.
|
||||||
|
*/
|
||||||
|
public bool $showFollowDialog = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user whose profile is being viewed.
|
||||||
|
*/
|
||||||
|
#[Locked]
|
||||||
|
public User $profileUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the component is initialized.
|
||||||
|
*/
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->profileUser = User::select(['id', 'name', 'profile_photo_path', 'cover_photo_path'])
|
||||||
|
->findOrFail($this->profileUserId);
|
||||||
|
|
||||||
|
$this->setTitle();
|
||||||
|
$this->setEmptyMessage();
|
||||||
|
$this->setDialogTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the title of the card based on the relationship.
|
||||||
|
*/
|
||||||
|
private function setTitle(): void
|
||||||
|
{
|
||||||
|
$this->title = match ($this->relationship) {
|
||||||
|
'followers' => __('Followers'),
|
||||||
|
'following' => __('Following'),
|
||||||
|
default => __('Users'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the empty message based on the relationship.
|
||||||
|
*/
|
||||||
|
private function setEmptyMessage(): void
|
||||||
|
{
|
||||||
|
$this->emptyMessage = match ($this->relationship) {
|
||||||
|
'followers' => __('No followers yet.'),
|
||||||
|
'following' => __('Not yet following anyone.'),
|
||||||
|
default => __('No users found.'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the dialog title based on the relationship.
|
||||||
|
*/
|
||||||
|
private function setDialogTitle(): void
|
||||||
|
{
|
||||||
|
$this->dialogTitle = match ($this->relationship) {
|
||||||
|
'followers' => 'User :name has these followers:',
|
||||||
|
'following' => 'User :name is following:',
|
||||||
|
default => 'Users:',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
$this->populateFollowUsers();
|
||||||
|
|
||||||
|
return view('livewire.user.follow-card');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the user follows or unfollows a user.
|
||||||
|
*/
|
||||||
|
#[On('user-follow-change')]
|
||||||
|
public function populateFollowUsers(): void
|
||||||
|
{
|
||||||
|
// Fetch IDs of all users the authenticated user is following.
|
||||||
|
$followingIds = auth()->user()->following()->pluck('following_id');
|
||||||
|
|
||||||
|
// Load the profile user's followers (or following) and map the follow status.
|
||||||
|
$this->followUsers = $this->profileUser->{$this->relationship}
|
||||||
|
->map(function (User $user) use ($followingIds) {
|
||||||
|
// Add the follow status based on the preloaded IDs.
|
||||||
|
$user->follows = $followingIds->contains($user->id);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle showing the follow dialog.
|
||||||
|
*/
|
||||||
|
public function toggleFollowDialog(): void
|
||||||
|
{
|
||||||
|
$this->showFollowDialog = ! $this->showFollowDialog;
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ use App\Notifications\VerifyEmail;
|
|||||||
use App\Traits\HasCoverPhoto;
|
use App\Traits\HasCoverPhoto;
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
@ -52,6 +53,65 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
return $this->belongsToMany(Mod::class);
|
return $this->belongsToMany(Mod::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The relationship between a user and users that follow them.
|
||||||
|
*
|
||||||
|
* @return BelongsToMany<User>
|
||||||
|
*/
|
||||||
|
public function followers(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(User::class, 'user_follows', 'following_id', 'follower_id')
|
||||||
|
->withTimestamps();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Follow another user.
|
||||||
|
*/
|
||||||
|
public function follow(User|int $user): void
|
||||||
|
{
|
||||||
|
$userId = $user instanceof User ? $user->id : $user;
|
||||||
|
|
||||||
|
if ($this->id === $userId) {
|
||||||
|
// Don't allow following yourself.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->following()->syncWithoutDetaching([$userId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The relationship between a user and users they follow.
|
||||||
|
*
|
||||||
|
* @return BelongsToMany<User>
|
||||||
|
*/
|
||||||
|
public function following(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(User::class, 'user_follows', 'follower_id', 'following_id')
|
||||||
|
->withTimestamps();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unfollow another user.
|
||||||
|
*/
|
||||||
|
public function unfollow(User|int $user): void
|
||||||
|
{
|
||||||
|
$userId = $user instanceof User ? $user->id : $user;
|
||||||
|
|
||||||
|
if ($this->isFollowing($userId)) {
|
||||||
|
$this->following()->detach($userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user is following another user.
|
||||||
|
*/
|
||||||
|
public function isFollowing(User|int $user): bool
|
||||||
|
{
|
||||||
|
$userId = $user instanceof User ? $user->id : $user;
|
||||||
|
|
||||||
|
return $this->following()->where('following_id', $userId)->exists();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data that is searchable by Scout.
|
* The data that is searchable by Scout.
|
||||||
*/
|
*/
|
||||||
@ -68,7 +128,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
*/
|
*/
|
||||||
public function shouldBeSearchable(): bool
|
public function shouldBeSearchable(): bool
|
||||||
{
|
{
|
||||||
return ! is_null($this->email_verified_at);
|
return $this->isNotBanned();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -150,6 +210,24 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
return $filters->apply($builder);
|
return $filters->apply($builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the about default value if empty. Thanks MySQL!
|
||||||
|
*/
|
||||||
|
protected function about(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: function ($value) {
|
||||||
|
// MySQL will not allow you to set a default value of an empty string for a (LONG)TEXT column. *le sigh*
|
||||||
|
// NULL is the default. If NULL is saved, we'll swap it out for an empty string.
|
||||||
|
if (is_null($value)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the disk that profile photos should be stored on.
|
* Get the disk that profile photos should be stored on.
|
||||||
*/
|
*/
|
||||||
@ -164,8 +242,12 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'id' => 'integer',
|
||||||
|
'hub_id' => 'integer',
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
|
'created_at' => 'datetime',
|
||||||
|
'updated_at' => 'datetime',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,8 @@
|
|||||||
"laravel/pint": "^1.16",
|
"laravel/pint": "^1.16",
|
||||||
"laravel/sail": "^1.29",
|
"laravel/sail": "^1.29",
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"nunomaduro/collision": "^8.1",
|
"nunomaduro/collision": "^8.4",
|
||||||
"pestphp/pest": "^2.34",
|
"pestphp/pest": "^3.0",
|
||||||
"spatie/laravel-ignition": "^2.8"
|
"spatie/laravel-ignition": "^2.8"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
1002
composer.lock
generated
1002
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -114,7 +114,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'inject_assets' => true,
|
'inject_assets' => false,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|---------------------------------------------------------------------------
|
|---------------------------------------------------------------------------
|
||||||
|
@ -6,6 +6,7 @@ use App\Models\User;
|
|||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Random\RandomException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends Factory<User>
|
* @extends Factory<User>
|
||||||
@ -21,6 +22,8 @@ class UserFactory extends Factory
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the user's default state.
|
* Define the user's default state.
|
||||||
|
*
|
||||||
|
* @throws RandomException
|
||||||
*/
|
*/
|
||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
@ -29,6 +32,7 @@ class UserFactory extends Factory
|
|||||||
'email' => fake()->unique()->safeEmail(),
|
'email' => fake()->unique()->safeEmail(),
|
||||||
'email_verified_at' => now(),
|
'email_verified_at' => now(),
|
||||||
'password' => static::$password ??= Hash::make('password'),
|
'password' => static::$password ??= Hash::make('password'),
|
||||||
|
'about' => fake()->paragraphs(random_int(1, 10), true),
|
||||||
'two_factor_secret' => null,
|
'two_factor_secret' => null,
|
||||||
'two_factor_recovery_codes' => null,
|
'two_factor_recovery_codes' => null,
|
||||||
'remember_token' => Str::random(10),
|
'remember_token' => Str::random(10),
|
||||||
|
@ -22,6 +22,7 @@ return new class extends Migration
|
|||||||
$table->string('email')->unique();
|
$table->string('email')->unique();
|
||||||
$table->timestamp('email_verified_at')->nullable();
|
$table->timestamp('email_verified_at')->nullable();
|
||||||
$table->string('password');
|
$table->string('password');
|
||||||
|
$table->longText('about')->nullable()->default(null);
|
||||||
$table->foreignIdFor(UserRole::class)
|
$table->foreignIdFor(UserRole::class)
|
||||||
->nullable()
|
->nullable()
|
||||||
->default(null)
|
->default(null)
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('user_follows', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('follower_id')->references('id')->on('users')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
$table->foreignId('following_id')->references('id')->on('users')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('user_follows');
|
||||||
|
}
|
||||||
|
};
|
158
package-lock.json
generated
158
package-lock.json
generated
@ -556,9 +556,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
|
||||||
"integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==",
|
"integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -570,9 +570,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz",
|
||||||
"integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==",
|
"integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -584,9 +584,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz",
|
||||||
"integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==",
|
"integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -598,9 +598,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz",
|
||||||
"integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==",
|
"integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -612,9 +612,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz",
|
||||||
"integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==",
|
"integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -626,9 +626,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz",
|
||||||
"integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==",
|
"integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -640,9 +640,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz",
|
||||||
"integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==",
|
"integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -654,9 +654,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz",
|
||||||
"integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==",
|
"integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -668,9 +668,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz",
|
||||||
"integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==",
|
"integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@ -682,9 +682,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz",
|
||||||
"integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==",
|
"integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@ -696,9 +696,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz",
|
||||||
"integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==",
|
"integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@ -710,9 +710,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz",
|
||||||
"integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==",
|
"integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -724,9 +724,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz",
|
||||||
"integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==",
|
"integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -738,9 +738,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz",
|
||||||
"integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==",
|
"integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -752,9 +752,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz",
|
||||||
"integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==",
|
"integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@ -766,9 +766,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz",
|
||||||
"integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==",
|
"integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -1013,9 +1013,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001660",
|
"version": "1.0.30001663",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz",
|
||||||
"integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==",
|
"integrity": "sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -1161,9 +1161,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.23",
|
"version": "1.5.27",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.23.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz",
|
||||||
"integrity": "sha512-mBhODedOXg4v5QWwl21DjM5amzjmI1zw9EPrPK/5Wx7C8jt33bpZNrC7OhHUG3pxRtbLpr3W2dXT+Ph1SsfRZA==",
|
"integrity": "sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
@ -2176,9 +2176,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.21.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz",
|
||||||
"integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==",
|
"integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2192,22 +2192,22 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.21.3",
|
"@rollup/rollup-android-arm-eabi": "4.22.4",
|
||||||
"@rollup/rollup-android-arm64": "4.21.3",
|
"@rollup/rollup-android-arm64": "4.22.4",
|
||||||
"@rollup/rollup-darwin-arm64": "4.21.3",
|
"@rollup/rollup-darwin-arm64": "4.22.4",
|
||||||
"@rollup/rollup-darwin-x64": "4.21.3",
|
"@rollup/rollup-darwin-x64": "4.22.4",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.21.3",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.22.4",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.21.3",
|
"@rollup/rollup-linux-arm-musleabihf": "4.22.4",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.21.3",
|
"@rollup/rollup-linux-arm64-gnu": "4.22.4",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.21.3",
|
"@rollup/rollup-linux-arm64-musl": "4.22.4",
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.3",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.22.4",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.21.3",
|
"@rollup/rollup-linux-riscv64-gnu": "4.22.4",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.21.3",
|
"@rollup/rollup-linux-s390x-gnu": "4.22.4",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.21.3",
|
"@rollup/rollup-linux-x64-gnu": "4.22.4",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.21.3",
|
"@rollup/rollup-linux-x64-musl": "4.22.4",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.21.3",
|
"@rollup/rollup-win32-arm64-msvc": "4.22.4",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.21.3",
|
"@rollup/rollup-win32-ia32-msvc": "4.22.4",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.21.3",
|
"@rollup/rollup-win32-x64-msvc": "4.22.4",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2428,9 +2428,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.11",
|
"version": "3.4.13",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",
|
||||||
"integrity": "sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==",
|
"integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2574,9 +2574,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.5",
|
"version": "5.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz",
|
||||||
"integrity": "sha512-pXqR0qtb2bTwLkev4SE3r4abCNioP3GkjvIDLlzziPpXtHgiJIjuKl+1GN6ESOT3wMjG3JTeARopj2SwYaHTOA==",
|
"integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
File diff suppressed because one or more lines are too long
@ -13,7 +13,7 @@ cropperjs/dist/cropper.min.css:
|
|||||||
|
|
||||||
filepond/dist/filepond.min.css:
|
filepond/dist/filepond.min.css:
|
||||||
(*!
|
(*!
|
||||||
* FilePond 4.31.1
|
* FilePond 4.31.3
|
||||||
* Licensed under MIT, https://opensource.org/licenses/MIT/
|
* Licensed under MIT, https://opensource.org/licenses/MIT/
|
||||||
* Please visit https://pqina.nl/filepond/ for details.
|
* Please visit https://pqina.nl/filepond/ for details.
|
||||||
*)
|
*)
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -108,3 +108,66 @@ main a:not(.mod-list-component):not(.tab):not([role="menuitem"]) {
|
|||||||
transform-origin: 100% 100%;
|
transform-origin: 100% 100%;
|
||||||
background-color: #0e7490;
|
background-color: #0e7490;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rainbow {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
background: linear-gradient(124deg, #ff2400, #e81d1d, #e8b71d, #e3e81d, #1de840, #1ddde8, #2b1de8, #dd00f3, #dd00f3);
|
||||||
|
background-size: 1800% 1800%;
|
||||||
|
-webkit-animation: rainbow 18s ease infinite;
|
||||||
|
-o-animation: rainbow 18s ease infinite;
|
||||||
|
animation: rainbow 18s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes rainbow {
|
||||||
|
0% {
|
||||||
|
background-position: 0 82%
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 19%
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 82%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-moz-keyframes rainbow {
|
||||||
|
0% {
|
||||||
|
background-position: 0 82%
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 19%
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 82%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-o-keyframes rainbow {
|
||||||
|
0% {
|
||||||
|
background-position: 0 82%
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 19%
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 82%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rainbow {
|
||||||
|
0% {
|
||||||
|
background-position: 0 82%
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 19%
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 82%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<a href="/mod/{{ $result['id'] }}/{{ $result['slug'] }}" class="{{ $linkClass }}" role="listitem" tabindex="0" class="flex flex-col">
|
<a href="/mod/{{ $result['id'] }}/{{ $result['slug'] }}" class="{{ $linkClass }}" role="listitem" tabindex="0" class="flex flex-col">
|
||||||
@if(empty($result->thumbnail))
|
@empty ($result['thumbnail'])
|
||||||
<img src="https://placehold.co/450x450/EEE/31343C?font=source-sans-pro&text={{ $result['name'] }}" alt="{{ $result['name'] }}" class="block dark:hidden h-6 w-6 self-center border border-gray-200 group-hover/global-search-link:border-gray-400">
|
<img src="https://placehold.co/450x450/31343C/EEE?font=source-sans-pro&text={{ urlencode($result['name']) }}" alt="{{ $result['name'] }}" class="h-6 w-6 self-center border border-gray-700 group-hover/global-search-link:border-gray-600">
|
||||||
<img src="https://placehold.co/450x450/31343C/EEE?font=source-sans-pro&text={{ $result['name'] }}" alt="{{ $result['name'] }}" class="hidden dark:block h-6 w-6 self-center border border-gray-700 group-hover/global-search-link:border-gray-600">
|
|
||||||
@else
|
@else
|
||||||
<img src="{{ Storage::url($result['thumbnail']) }}" alt="{{ $result['name'] }}" class="h-6 w-6 self-center">
|
<img src="{{ Storage::url($result['thumbnail']) }}" alt="{{ $result['name'] }}" class="h-6 w-6 self-center">
|
||||||
@endif
|
@endempty
|
||||||
<p class="flex-grow">{{ $result['name'] }}</p>
|
<p class="flex-grow">{{ $result['name'] }}</p>
|
||||||
<p class="ml-auto self-center badge-version {{ $result['latestVersionColorClass'] }} }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
<p class="ml-auto self-center badge-version {{ $result['latestVersionColorClass'] }} }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||||
{{ $result['latestVersion'] }}
|
{{ $result['latestVersion'] }}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<a href="#/{{ Str::slug($result['name']) }}" class="{{ $linkClass }}">
|
<a href="/user/{{ $result['id'] }}/{{ Str::slug($result['name']) }}" class="{{ $linkClass }}">
|
||||||
<p>{{ $result['name'] }}</p>
|
<p>{{ $result['name'] }}</p>
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
<div id="search-results"
|
|
||||||
x-cloak
|
|
||||||
x-show="showDropdown && query.length"
|
|
||||||
x-transition
|
|
||||||
aria-live="polite"
|
|
||||||
class="{{ $showDropdown ? 'block' : 'hidden' }} absolute z-10 top-11 w-full mx-auto max-w-2xl transform overflow-hidden rounded-md bg-white dark:bg-gray-900 shadow-2xl border border-gray-300 dark:border-gray-700 transition-all"
|
|
||||||
>
|
|
||||||
@if ($showDropdown)
|
|
||||||
<h2 class="sr-only">{{ __('Search Results') }}</h2>
|
|
||||||
<div class="max-h-96 scroll-py-2 overflow-y-auto" role="list">
|
|
||||||
@foreach($results['data'] as $typeName => $typeResults)
|
|
||||||
@if($typeResults->count())
|
|
||||||
<h4 class="flex flex-row gap-1.5 py-2.5 px-4 text-[0.6875rem] font-semibold uppercase text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-950">
|
|
||||||
<span>{{ Str::plural($typeName) }}</span>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
|
|
||||||
</svg>
|
|
||||||
</h4>
|
|
||||||
<div class="divide-y divide-dashed divide-gray-200 dark:divide-gray-800">
|
|
||||||
@foreach($typeResults as $result)
|
|
||||||
@component('components.global-search-result-' . Str::lower($typeName), [
|
|
||||||
'result' => $result,
|
|
||||||
'linkClass' => 'group/global-search-link flex flex-row gap-3 py-1.5 px-4 text-gray-900 dark:text-gray-100 hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors duration-200 ease-in-out',
|
|
||||||
])
|
|
||||||
@endcomponent
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if($noResults)
|
|
||||||
<div class="px-6 py-14 text-center sm:px-14">
|
|
||||||
<svg class="mx-auto h-6 w-6 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m5.231 13.481L15 17.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v16.5c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Zm3.75 11.625a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" />
|
|
||||||
</svg>
|
|
||||||
<p class="mt-4 text-sm text-gray-900 dark:text-gray-200">{{ __("We couldn't find any content with that query. Please try again.") }}</p>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
@ -7,20 +7,21 @@
|
|||||||
<div class="flex flex-col group h-full w-full bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl overflow-hidden hover:shadow-lg hover:bg-gray-50 dark:hover:bg-black hover:shadow-gray-400 dark:hover:shadow-black transition-all duration-200">
|
<div class="flex flex-col group h-full w-full bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl overflow-hidden hover:shadow-lg hover:bg-gray-50 dark:hover:bg-black hover:shadow-gray-400 dark:hover:shadow-black transition-all duration-200">
|
||||||
<div class="h-auto md:h-full md:flex">
|
<div class="h-auto md:h-full md:flex">
|
||||||
<div class="h-auto md:h-full md:shrink-0 overflow-hidden">
|
<div class="h-auto md:h-full md:shrink-0 overflow-hidden">
|
||||||
@empty($mod->thumbnail)
|
@if ($mod->thumbnail)
|
||||||
<img src="https://placehold.co/450x450/EEE/31343C?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="block dark:hidden h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
|
||||||
<img src="https://placehold.co/450x450/31343C/EEE?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="hidden dark:block h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
|
||||||
@else
|
|
||||||
<img src="{{ $mod->thumbnailUrl }}" alt="{{ $mod->name }}" class="h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
<img src="{{ $mod->thumbnailUrl }}" alt="{{ $mod->name }}" class="h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
||||||
@endempty
|
@else
|
||||||
|
<img src="https://placehold.co/450x450/31343C/EEE?font=source-sans-pro&text={{ urlencode($mod->name) }}" alt="{{ $mod->name }}" class="h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col w-full justify-between p-5">
|
<div class="flex flex-col w-full justify-between p-5">
|
||||||
<div class="pb-3">
|
<div class="pb-3">
|
||||||
<div class="flex justify-between items-center space-x-3">
|
<div class="flex justify-between items-center space-x-3">
|
||||||
<h3 class="block mt-1 text-lg leading-tight font-medium text-black dark:text-white group-hover:underline">{{ $mod->name }}</h3>
|
<h3 class="block mt-1 text-lg leading-tight font-medium text-black dark:text-white group-hover:underline">{{ $mod->name }}</h3>
|
||||||
<span class="badge-version {{ $version->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
@if ($version?->latestSptVersion)
|
||||||
{{ $version->latestSptVersion->version_formatted }}
|
<span class="badge-version {{ $version->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||||
</span>
|
{{ $version->latestSptVersion->version_formatted }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm italic text-slate-600 dark:text-gray-200">
|
<p class="text-sm italic text-slate-600 dark:text-gray-200">
|
||||||
{{ __('By :authors', ['authors' => $mod->users->pluck('name')->implode(', ')]) }}
|
{{ __('By :authors', ['authors' => $mod->users->pluck('name')->implode(', ')]) }}
|
||||||
@ -31,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-slate-700 dark:text-gray-300 text-sm">
|
<div class="text-slate-700 dark:text-gray-300 text-sm">
|
||||||
<div class="flex items-end w-full text-sm">
|
<div class="flex items-end w-full text-sm">
|
||||||
@if ($mod->updated_at || $mod->created_at)
|
@if (($mod->updated_at || $mod->created_at) && $version)
|
||||||
<div class="flex items-end w-full">
|
<div class="flex items-end w-full">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||||
|
11
resources/views/components/profile-button.blade.php
Normal file
11
resources/views/components/profile-button.blade.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
@props(['name'])
|
||||||
|
|
||||||
|
<div class="flex flex-col justify-stretch sm:flex-row">
|
||||||
|
<button {{ $attributes->whereStartsWith('wire:') }} {{ $attributes->merge([
|
||||||
|
'type' => 'button',
|
||||||
|
'class' => 'inline-flex justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600',
|
||||||
|
]) }}>
|
||||||
|
{{ $icon }}
|
||||||
|
<span>{{ $slot }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
17
resources/views/components/tab-button.blade.php
Normal file
17
resources/views/components/tab-button.blade.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
@props(['name'])
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="selectedTab = '{{ Str::lower($name) }}'"
|
||||||
|
:aria-selected="selectedTab == '{{ Str::lower($name) }}'"
|
||||||
|
:class="{
|
||||||
|
'font-extrabold': selectedTab == '{{ Str::lower($name) }}',
|
||||||
|
'font-light': selectedTab != '{{ Str::lower($name) }}',
|
||||||
|
'tab group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm text-gray-800 dark:text-white bg-cyan-500 dark:bg-cyan-700 hover:bg-cyan-400 dark:hover:bg-cyan-600 focus:z-10 last:rounded-r-xl first:rounded-l-xl flex items-center justify-center gap-1': true
|
||||||
|
}"
|
||||||
|
{{ $attributes }}
|
||||||
|
>
|
||||||
|
{{ __($name) }}
|
||||||
|
<svg x-cloak x-show="selectedTab == '{{ Str::lower($name) }}'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="h-3 h-3">
|
||||||
|
<path fill-rule="evenodd" d="M7.47 12.78a.75.75 0 0 0 1.06 0l3.25-3.25a.75.75 0 0 0-1.06-1.06L8 11.19 5.28 8.47a.75.75 0 0 0-1.06 1.06l3.25 3.25ZM4.22 4.53l3.25 3.25a.75.75 0 0 0 1.06 0l3.25-3.25a.75.75 0 0 0-1.06-1.06L8 6.19 5.28 3.47a.75.75 0 0 0-1.06 1.06Z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
@ -1,5 +1,5 @@
|
|||||||
@props(['datetime'])
|
@props(['datetime'])
|
||||||
|
|
||||||
<time datetime="{{ $datetime->format('c') }}">
|
<time datetime="{{ $datetime->format('c') }}" title="{{ $datetime->format('l jS \\of F Y g:i:s A e') }}">
|
||||||
{{ Carbon::dynamicFormat($datetime) }}
|
{{ Carbon::dynamicFormat($datetime) }}
|
||||||
</time>
|
</time>
|
||||||
|
8
resources/views/components/user-follow-cards.blade.php
Normal file
8
resources/views/components/user-follow-cards.blade.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
@props(['profileUserId'])
|
||||||
|
|
||||||
|
<div class="flex w-full max-w-sm">
|
||||||
|
<livewire:user.follow-card relationship="followers" :profile-user-id="$profileUserId" />
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full max-w-sm">
|
||||||
|
<livewire:user.follow-card relationship="following" :profile-user-id="$profileUserId" />
|
||||||
|
</div>
|
@ -1,3 +1,4 @@
|
|||||||
<div class="items-center justify-between gap-x-6 text-gray-200 bg-gray-900 dark:text-gray-900 dark:bg-gray-100 px-6 py-2.5 sm:pr-3.5 lg:pl-8">
|
<div class="relative items-center justify-between gap-x-6 text-gray-900 px-6 py-2.5 sm:pr-3.5 lg:pl-8">
|
||||||
<p class="text-center text-sm leading-6">Notice: The Forge is currently under <em class="font-bold">heavy</em> construction. Expect nothing to work. Data is reset every hour.</p>
|
<div class="rainbow z-0"></div>
|
||||||
|
<p class="relative z-10 text-center text-sm leading-6 font-bold" style="text-shadow: 0 0 15px white;">{!! __('The Forge is currently under <em>heavy</em> construction. Expect nothing to work. Data is reset every hour.') !!}</p>
|
||||||
</div>
|
</div>
|
||||||
|
1
resources/views/includes/analytics.blade.php
Normal file
1
resources/views/includes/analytics.blade.php
Normal file
@ -0,0 +1 @@
|
|||||||
|
<script defer src="https://umami.refringe.com/script.js" data-website-id="3cf2977d-3d62-48ad-b6be-5a2d2e8f1d84"></script>
|
@ -8,11 +8,15 @@
|
|||||||
<title>{{ config('app.name', 'The Forge') }}</title>
|
<title>{{ config('app.name', 'The Forge') }}</title>
|
||||||
|
|
||||||
<link rel="icon" href="data:image/x-icon;base64,AA">
|
<link rel="icon" href="data:image/x-icon;base64,AA">
|
||||||
|
|
||||||
<link href="//fonts.bunny.net" rel="preconnect">
|
<link href="//fonts.bunny.net" rel="preconnect">
|
||||||
<link href="//fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet">
|
<link href="//fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<link href="{{ config('app.asset_url') }}" rel="dns-prefetch">
|
<link href="{{ config('app.asset_url') }}" rel="dns-prefetch">
|
||||||
|
|
||||||
|
@livewireStyles
|
||||||
|
@vite(['resources/css/app.css'])
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Immediately set the theme to prevent a flash of the default theme when another is set.
|
// Immediately set the theme to prevent a flash of the default theme when another is set.
|
||||||
// Must be located inline, in the head, and before any CSS is loaded.
|
// Must be located inline, in the head, and before any CSS is loaded.
|
||||||
@ -25,35 +29,33 @@
|
|||||||
document.documentElement.classList.add(theme);
|
document.documentElement.classList.add(theme);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
||||||
@livewireStyles
|
|
||||||
</head>
|
</head>
|
||||||
<body class="font-sans antialiased">
|
<body class="font-sans antialiased">
|
||||||
|
<x-warning/>
|
||||||
|
|
||||||
<x-warning/>
|
<x-banner/>
|
||||||
|
|
||||||
<x-banner/>
|
<div class="min-h-screen bg-gray-100 dark:bg-gray-800">
|
||||||
|
@livewire('navigation-menu')
|
||||||
|
|
||||||
<div class="min-h-screen bg-gray-100 dark:bg-gray-800">
|
@if (isset($header))
|
||||||
@livewire('navigation-menu')
|
<header class="bg-gray-50 dark:bg-gray-900 shadow dark:shadow-gray-950">
|
||||||
|
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||||
|
{{ $header }}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
@endif
|
||||||
|
|
||||||
@if (isset($header))
|
<main class="pb-6 sm:py-12">
|
||||||
<header class="bg-gray-50 dark:bg-gray-900 shadow dark:shadow-gray-950">
|
{{ $slot }}
|
||||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
</main>
|
||||||
{{ $header }}
|
</div>
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<main class="pb-6 sm:py-12">
|
<x-footer/>
|
||||||
{{ $slot }}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<x-footer/>
|
@vite(['resources/js/app.js'])
|
||||||
|
@stack('modals')
|
||||||
@stack('modals')
|
@livewireScriptConfig
|
||||||
@livewireScriptConfig
|
@include('includes.analytics')
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -8,11 +8,15 @@
|
|||||||
<title>{{ config('app.name', 'The Forge') }}</title>
|
<title>{{ config('app.name', 'The Forge') }}</title>
|
||||||
|
|
||||||
<link rel="icon" href="data:image/x-icon;base64,AA">
|
<link rel="icon" href="data:image/x-icon;base64,AA">
|
||||||
|
|
||||||
<link href="//fonts.bunny.net" rel="preconnect">
|
<link href="//fonts.bunny.net" rel="preconnect">
|
||||||
<link href="//fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet">
|
<link href="//fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<link href="{{ config('app.asset_url') }}" rel="dns-prefetch">
|
<link href="{{ config('app.asset_url') }}" rel="dns-prefetch">
|
||||||
|
|
||||||
|
@vite(['resources/css/app.css'])
|
||||||
|
@livewireStyles
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Immediately set the theme to prevent a flash of the default theme when another is set.
|
// Immediately set the theme to prevent a flash of the default theme when another is set.
|
||||||
// Must be located inline, in the head, and before any CSS is loaded.
|
// Must be located inline, in the head, and before any CSS is loaded.
|
||||||
@ -25,16 +29,14 @@
|
|||||||
document.documentElement.classList.add(theme);
|
document.documentElement.classList.add(theme);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
||||||
@livewireStyles
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div class="font-sans text-gray-900 antialiased">
|
||||||
|
{{ $slot }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="font-sans text-gray-900 antialiased">
|
@vite(['resources/js/app.js']);
|
||||||
{{ $slot }}
|
@livewireScriptConfig
|
||||||
</div>
|
@include('includes.analytics')
|
||||||
|
|
||||||
@livewireScriptConfig
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
<div x-data="{ query: $wire.entangle('query'), showDropdown: $wire.entangle('showDropdown'), noResults: $wire.entangle('noResults') }"
|
<div
|
||||||
@keydown.esc.window="showDropdown = false"
|
x-data="{ query: $wire.entangle('query'), count: $wire.entangle('count'), show: false }"
|
||||||
class="flex flex-1 justify-center px-2 lg:ml-6 lg:justify-end"
|
class="flex flex-1 justify-center px-2 lg:ml-6 lg:justify-end"
|
||||||
>
|
>
|
||||||
<div class="w-full max-w-lg lg:max-w-md"
|
<div class="w-full max-w-lg lg:max-w-md">
|
||||||
x-trap="showDropdown && query.length"
|
|
||||||
@click.away="showDropdown = false"
|
|
||||||
@keydown.down.prevent="$focus.wrap().next()"
|
|
||||||
@keydown.up.prevent="$focus.wrap().previous()"
|
|
||||||
>
|
|
||||||
<label for="search" class="sr-only">{{ __('Search') }}</label>
|
<label for="search" class="sr-only">{{ __('Search') }}</label>
|
||||||
<search class="relative group" role="search">
|
<search
|
||||||
|
x-trap.noreturn="query.length && show"
|
||||||
|
@click.away="show = false"
|
||||||
|
@keydown.down.prevent="$focus.wrap().next()"
|
||||||
|
@keydown.up.prevent="$focus.wrap().previous()"
|
||||||
|
@keydown.escape.window="$wire.query = '';"
|
||||||
|
class="relative group"
|
||||||
|
role="search"
|
||||||
|
>
|
||||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||||
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
<path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd" />
|
||||||
@ -18,15 +21,50 @@
|
|||||||
<input id="global-search"
|
<input id="global-search"
|
||||||
type="search"
|
type="search"
|
||||||
wire:model.live="query"
|
wire:model.live="query"
|
||||||
@focus="showDropdown = true"
|
@focus="show = true"
|
||||||
@keydown.escape.window="$wire.query = ''; showDropdown = false; $wire.$refresh()"
|
|
||||||
placeholder="{{ __('Search') }}"
|
placeholder="{{ __('Search') }}"
|
||||||
aria-controls="search-results"
|
aria-controls="search-results"
|
||||||
:aria-expanded="showDropdown"
|
|
||||||
aria-label="{{ __('Search') }}"
|
aria-label="{{ __('Search') }}"
|
||||||
class="block w-full rounded-md border-0 bg-white dark:bg-gray-700 py-1.5 pl-10 pr-3 text-gray-900 dark:text-gray-300 ring-1 ring-inset ring-gray-300 dark:ring-gray-700 placeholder:text-gray-400 dark:placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-gray-600 dark:focus:bg-gray-200 dark:focus:text-black dark:focus:ring-0 sm:text-sm sm:leading-6"
|
class="block w-full rounded-md border-0 bg-white dark:bg-gray-700 py-1.5 pl-10 pr-3 text-gray-900 dark:text-gray-300 ring-1 ring-inset ring-gray-300 dark:ring-gray-700 placeholder:text-gray-400 dark:placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-gray-600 dark:focus:bg-gray-200 dark:focus:text-black dark:focus:ring-0 sm:text-sm sm:leading-6"
|
||||||
/>
|
/>
|
||||||
<x-global-search-results :showDropdown="$showDropdown" :noResults="$noResults" :results="$results" />
|
<div id="search-results"
|
||||||
|
x-cloak
|
||||||
|
x-transition
|
||||||
|
x-show="query.length && show"
|
||||||
|
aria-live="polite"
|
||||||
|
class="absolute z-10 top-11 w-full mx-auto max-w-2xl transform overflow-hidden rounded-md bg-white dark:bg-gray-900 shadow-2xl border border-gray-300 dark:border-gray-700 transition-all"
|
||||||
|
>
|
||||||
|
<div x-cloak x-show="count">
|
||||||
|
<h2 class="sr-only select-none">{{ __('Search Results') }}</h2>
|
||||||
|
<div class="max-h-96 scroll-py-2 overflow-y-auto" role="list" tabindex="-1">
|
||||||
|
@foreach($result as $type => $results)
|
||||||
|
@if ($results->count())
|
||||||
|
<h4 class="flex flex-row gap-1.5 py-2.5 px-4 text-[0.6875rem] font-semibold uppercase text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-950 select-none">
|
||||||
|
<span>{{ Str::plural($type) }}</span>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
|
||||||
|
</svg>
|
||||||
|
</h4>
|
||||||
|
<div class="divide-y divide-dashed divide-gray-200 dark:divide-gray-800">
|
||||||
|
@foreach($results as $hit)
|
||||||
|
@component('components.global-search-result-' . Str::lower($type), [
|
||||||
|
'result' => $hit,
|
||||||
|
'linkClass' => 'group/global-search-link flex flex-row gap-3 py-1.5 px-4 text-gray-900 dark:text-gray-100 hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors duration-200 ease-in-out',
|
||||||
|
])
|
||||||
|
@endcomponent
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="count < 1" class="px-6 py-14 text-center sm:px-14">
|
||||||
|
<svg class="mx-auto h-6 w-6 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m5.231 13.481L15 17.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v16.5c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Zm3.75 11.625a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" />
|
||||||
|
</svg>
|
||||||
|
<p class="mt-4 text-sm text-gray-900 dark:text-gray-200">{{ __("We couldn't find any content with that query. Please try again.") }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</search>
|
</search>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
25
resources/views/livewire/user/follow-buttons.blade.php
Normal file
25
resources/views/livewire/user/follow-buttons.blade.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
@props(['isFollowing'])
|
||||||
|
|
||||||
|
<form>
|
||||||
|
@if ($isFollowing)
|
||||||
|
{{-- Following button --}}
|
||||||
|
<x-profile-button wire:click="unfollow">
|
||||||
|
<x-slot:icon>
|
||||||
|
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-red-400 dark:text-red-600" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="m12.82 5.58-.82.822-.824-.824a5.375 5.375 0 1 0-7.601 7.602l7.895 7.895a.75.75 0 0 0 1.06 0l7.902-7.897a5.376 5.376 0 0 0-.001-7.599 5.38 5.38 0 0 0-7.611 0Z" />
|
||||||
|
</svg>
|
||||||
|
</x-slot:icon>
|
||||||
|
{{ __('Following') }}
|
||||||
|
</x-profile-button>
|
||||||
|
@else
|
||||||
|
{{-- Follow button --}}
|
||||||
|
<x-profile-button wire:click="follow">
|
||||||
|
<x-slot:icon>
|
||||||
|
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400 dark:text-gray-300" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="m12.82 5.58-.82.822-.824-.824a5.375 5.375 0 1 0-7.601 7.602l7.895 7.895a.75.75 0 0 0 1.06 0l7.902-7.897a5.376 5.376 0 0 0-.001-7.599 5.38 5.38 0 0 0-7.611 0Zm6.548 6.54L12 19.485 4.635 12.12a3.875 3.875 0 1 1 5.48-5.48l1.358 1.357a.75.75 0 0 0 1.073-.012L13.88 6.64a3.88 3.88 0 0 1 5.487 5.48Z"/>
|
||||||
|
</svg>
|
||||||
|
</x-slot:icon>
|
||||||
|
{{ __('Follow') }}
|
||||||
|
</x-profile-button>
|
||||||
|
@endif
|
||||||
|
</form>
|
81
resources/views/livewire/user/follow-card.blade.php
Normal file
81
resources/views/livewire/user/follow-card.blade.php
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<div class="w-full text-gray-600 bg-white shadow-md dark:shadow-gray-950 drop-shadow-xl dark:text-gray-200 dark:bg-gray-900 rounded-xl py-4">
|
||||||
|
|
||||||
|
<div class="flex justify-center items-center">
|
||||||
|
<h2 class="text-2xl">{{ $title }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (! $followUsers->count())
|
||||||
|
<div class="flex justify-center text-sm pt-2">
|
||||||
|
{{ $emptyMessage }}
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="flex ml-6 py-2 justify-center items-center">
|
||||||
|
@foreach ($followUsers->slice(0, $limit) as $user)
|
||||||
|
{{-- User Badge --}}
|
||||||
|
<div class="relative group">
|
||||||
|
<a href="{{ $user->profileUrl() }}" class="rounded-full -ml-6 z-20 bg-[#ebf4ff] h-16 w-16 flex justify-center items-center border">
|
||||||
|
<img src="{{ $user->profile_photo_url }}" alt="{{ $user->name }}" class="h-full w-full rounded-full" />
|
||||||
|
</a>
|
||||||
|
<div class="absolute bottom-full -ml-3 left-1/2 transform -translate-x-1/2 mb-2 w-max px-2 py-1 text-sm text-white bg-gray-700 rounded shadow-lg opacity-0 group-hover:opacity-100">
|
||||||
|
{{ $user->name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
@if ($followUsers->count() > $limit)
|
||||||
|
{{-- Count Badge --}}
|
||||||
|
<div class="relative group">
|
||||||
|
<button wire:click="toggleFollowDialog" class="rounded-full -ml-6 z-20 bg-cyan-500 dark:bg-cyan-700 h-16 w-16 flex justify-center items-center border text-white">+{{ $followUsers->count() - $limit }}</button>
|
||||||
|
<div class="absolute bottom-full -ml-3 left-1/2 transform -translate-x-1/2 mb-2 w-max px-2 py-1 text-sm text-white bg-gray-700 rounded shadow-lg opacity-0 group-hover:opacity-100">
|
||||||
|
{{ $followUsers->count() }} total
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($followUsers->count() > $limit)
|
||||||
|
{{-- View All Button --}}
|
||||||
|
<div class="flex justify-center items-center">
|
||||||
|
<button wire:click="toggleFollowDialog" class="hover:underline active:underline">View All</button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- View All Dialog --}}
|
||||||
|
@push('modals')
|
||||||
|
<x-dialog-modal wire:model="showFollowDialog">
|
||||||
|
<x-slot name="title">
|
||||||
|
<h2 class="text-2xl">{{ __($dialogTitle, ['name' => $profileUser->name]) }}</h2>
|
||||||
|
</x-slot>
|
||||||
|
<x-slot name="content">
|
||||||
|
<div class="h-96 overflow-y-auto">
|
||||||
|
@foreach ($followUsers as $user)
|
||||||
|
<div class="flex group/item dark:hover:bg-gray-950 items-center p-2 pr-3 rounded-md">
|
||||||
|
<a href="{{ $user->profileUrl() }}" class="flex-shrink-0 w-16 h-16 items-center">
|
||||||
|
<img src="{{ $user->profile_photo_url }}" alt="{{ $user->name }}" class="block w-full h-full rounded-full" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="flex flex-col w-full pl-3">
|
||||||
|
<a href="{{ $user->profileUrl() }}" class="text-2xl group-hover/item:underline group-hover/item:text-white">{{ $user->name }}</a>
|
||||||
|
<span>
|
||||||
|
{{ __("Member Since") }}
|
||||||
|
<x-time :datetime="$user->created_at" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (auth()->check() && auth()->user()->id !== $user->id)
|
||||||
|
<livewire:user.follow-buttons :profile-user-id="$user->id" :is-following="$user->follows" />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<x-slot name="footer">
|
||||||
|
<x-button x-on:click="show = false">
|
||||||
|
{{ __('Close') }}
|
||||||
|
</x-button>
|
||||||
|
</x-slot>
|
||||||
|
</x-dialog-modal>
|
||||||
|
@endpush
|
||||||
|
</div>
|
@ -16,9 +16,8 @@
|
|||||||
@endif
|
@endif
|
||||||
<div class="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
<div class="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
||||||
<div class="grow-0 shrink-0 flex justify-center items-center">
|
<div class="grow-0 shrink-0 flex justify-center items-center">
|
||||||
@if (empty($mod->thumbnail))
|
@if ($mod->thumbnail)
|
||||||
<img src="https://placehold.co/144x144/EEE/31343C?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="block dark:hidden w-36 rounded-lg">
|
<img src="https://placehold.co/144x144/31343C/EEE?font=source-sans-pro&text={{ urlencode($mod->name) }}" alt="{{ $mod->name }}" class="w-36 rounded-lg">
|
||||||
<img src="https://placehold.co/144x144/31343C/EEE?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="hidden dark:block w-36 rounded-lg">
|
|
||||||
@else
|
@else
|
||||||
<img src="{{ $mod->thumbnailUrl }}" alt="{{ $mod->name }}" class="w-36 rounded-lg">
|
<img src="{{ $mod->thumbnailUrl }}" alt="{{ $mod->name }}" class="w-36 rounded-lg">
|
||||||
@endif
|
@endif
|
||||||
@ -39,11 +38,19 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</p>
|
</p>
|
||||||
<p title="{{ __('Exactly') }} {{ $mod->downloads }}">{{ Number::downloads($mod->downloads) }} {{ __('Downloads') }}</p>
|
<p title="{{ __('Exactly') }} {{ $mod->downloads }}">{{ Number::downloads($mod->downloads) }} {{ __('Downloads') }}</p>
|
||||||
<p class="mt-2">
|
@if ($mod->latestVersion->latestSptVersion)
|
||||||
<span class="badge-version {{ $mod->latestVersion->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
<p class="mt-2">
|
||||||
{{ $mod->latestVersion->latestSptVersion->version_formatted }} {{ __('Compatible') }}
|
<span class="badge-version {{ $mod->latestVersion->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||||
</span>
|
{{ $mod->latestVersion->latestSptVersion->version_formatted }} {{ __('Compatible') }}
|
||||||
</p>
|
</span>
|
||||||
|
</p>
|
||||||
|
@else
|
||||||
|
<p class="mt-2">
|
||||||
|
<span class="badge-version bg-gray-100 text-gray-600 inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||||
|
{{ __('Unknown SPT Version') }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -74,18 +81,9 @@
|
|||||||
{{-- Desktop Tabs --}}
|
{{-- Desktop Tabs --}}
|
||||||
<div class="hidden sm:block">
|
<div class="hidden sm:block">
|
||||||
<nav class="isolate flex divide-x divide-gray-200 dark:divide-gray-800 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl" aria-label="Tabs">
|
<nav class="isolate flex divide-x divide-gray-200 dark:divide-gray-800 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl" aria-label="Tabs">
|
||||||
<button @click="selectedTab = 'description'" class="tab rounded-l-xl group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10" aria-current="page">
|
<x-tab-button name="Description" />
|
||||||
<span>{{ __('Description') }}</span>
|
<x-tab-button name="Versions" />
|
||||||
<span aria-hidden="true" :class="selectedTab === 'description' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5'"></span>
|
<x-tab-button name="Comments" />
|
||||||
</button>
|
|
||||||
<button @click="selectedTab = 'versions'" class="tab group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
|
|
||||||
<span>{{ __('Versions') }}</span>
|
|
||||||
<span aria-hidden="true" :class="selectedTab === 'versions' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5'"></span>
|
|
||||||
</button>
|
|
||||||
<button @click="selectedTab = 'comments'" class="tab rounded-r-xl group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
|
|
||||||
<span>{{ __('Comments') }}</span>
|
|
||||||
<span aria-hidden="true" :class="selectedTab === 'comments' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5'"></span>
|
|
||||||
</button>
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -108,9 +106,15 @@
|
|||||||
<p class="text-gray-700 dark:text-gray-300" title="{{ __('Exactly') }} {{ $version->downloads }}">{{ Number::downloads($version->downloads) }} {{ __('Downloads') }}</p>
|
<p class="text-gray-700 dark:text-gray-300" title="{{ __('Exactly') }} {{ $version->downloads }}">{{ Number::downloads($version->downloads) }} {{ __('Downloads') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="badge-version {{ $version->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
@if ($version->latestSptVersion)
|
||||||
{{ $version->latestSptVersion->version_formatted }}
|
<span class="badge-version {{ $version->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||||
</span>
|
{{ $version->latestSptVersion->version_formatted }}
|
||||||
|
</span>
|
||||||
|
@else
|
||||||
|
<span class="badge-version bg-gray-100 text-gray-600 inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||||
|
{{ __('Unknown SPT Version') }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
<a href="{{ $version->virus_total_link }}">{{__('Virus Total Results')}}</a>
|
<a href="{{ $version->virus_total_link }}">{{__('Virus Total Results')}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between text-gray-600 dark:text-gray-400">
|
<div class="flex items-center justify-between text-gray-600 dark:text-gray-400">
|
||||||
@ -138,7 +142,7 @@
|
|||||||
|
|
||||||
{{-- Comments --}}
|
{{-- Comments --}}
|
||||||
<div x-show="selectedTab === 'comments'" class="user-markdown p-4 sm:p-6 bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl">
|
<div x-show="selectedTab === 'comments'" class="user-markdown p-4 sm:p-6 bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl">
|
||||||
<p>{{ __('The comments go here.') }}</p>
|
<p>Not quite yet...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,14 +87,12 @@
|
|||||||
</svg>
|
</svg>
|
||||||
{{ __('Edit Profile') }}
|
{{ __('Edit Profile') }}
|
||||||
</a>
|
</a>
|
||||||
@if (Laravel\Jetstream\Jetstream::hasApiFeatures())
|
<a href="{{ route('api-tokens.index') }}" class="flex items-center gap-2 bg-slate-100 px-4 py-2 text-sm text-slate-700 hover:bg-slate-800/5 hover:text-black focus-visible:bg-slate-800/10 focus-visible:text-black focus-visible:outline-none dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-100/5 dark:hover:text-white dark:focus-visible:bg-slate-100/10 dark:focus-visible:text-white" role="menuitem">
|
||||||
<a href="{{ route('api-tokens.index') }}" class="flex items-center gap-2 bg-slate-100 px-4 py-2 text-sm text-slate-700 hover:bg-slate-800/5 hover:text-black focus-visible:bg-slate-800/10 focus-visible:text-black focus-visible:outline-none dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-100/5 dark:hover:text-white dark:focus-visible:bg-slate-100/10 dark:focus-visible:text-white" role="menuitem">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0 1 12 15a9.065 9.065 0 0 0-6.23-.693L5 14.5m14.8.8 1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0 1 12 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5" />
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0 1 12 15a9.065 9.065 0 0 0-6.23-.693L5 14.5m14.8.8 1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0 1 12 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5" />
|
</svg>
|
||||||
</svg>
|
{{ __('API Tokens') }}
|
||||||
{{ __('API Tokens') }}
|
</a>
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
@if (auth()->user()->isAdmin())
|
@if (auth()->user()->isAdmin())
|
||||||
<div class="flex flex-col py-1.5">
|
<div class="flex flex-col py-1.5">
|
||||||
@ -147,9 +145,7 @@
|
|||||||
{{-- Mobile Menu --}}
|
{{-- Mobile Menu --}}
|
||||||
<div class="lg:hidden" x-show="mobileMenuOpen" id="mobile-menu">
|
<div class="lg:hidden" x-show="mobileMenuOpen" id="mobile-menu">
|
||||||
<div class="space-y-1 px-2 pb-3 pt-2">
|
<div class="space-y-1 px-2 pb-3 pt-2">
|
||||||
@auth
|
<x-responsive-nav-link href="{{ route('mods') }}" :active="request()->routeIs('mods')">{{ __('Mods') }}</x-responsive-nav-link>
|
||||||
<x-responsive-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')">{{ __('Dashboard') }}</x-responsive-nav-link>
|
|
||||||
@endauth
|
|
||||||
{{-- Additional menu links here --}}
|
{{-- Additional menu links here --}}
|
||||||
</div>
|
</div>
|
||||||
<div class="border-t border-gray-300 dark:border-gray-700 pb-3 pt-4">
|
<div class="border-t border-gray-300 dark:border-gray-700 pb-3 pt-4">
|
||||||
@ -178,8 +174,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-3 space-y-1 px-2">
|
<div class="mt-3 space-y-1 px-2">
|
||||||
@auth
|
@auth
|
||||||
<x-responsive-nav-link href="{{ route('profile.show') }}" :active="request()->routeIs('profile.show')">{{ __('Profile') }}</x-responsive-nav-link>
|
<x-responsive-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')">{{ __('Dashboard') }}</x-responsive-nav-link>
|
||||||
|
<x-responsive-nav-link href="{{ auth()->user()->profileUrl() }}" :active="request()->routeIs('user.show')">{{ __('Profile') }}</x-responsive-nav-link>
|
||||||
|
|
||||||
|
<x-responsive-nav-link href="{{ route('profile.show') }}" :active="request()->routeIs('profile.show')">{{ __('Edit Profile') }}</x-responsive-nav-link>
|
||||||
<x-responsive-nav-link href="{{ route('api-tokens.index') }}" :active="request()->routeIs('api-tokens.index')">{{ __('API Token') }}</x-responsive-nav-link>
|
<x-responsive-nav-link href="{{ route('api-tokens.index') }}" :active="request()->routeIs('api-tokens.index')">{{ __('API Token') }}</x-responsive-nav-link>
|
||||||
|
|
||||||
|
@if (auth()->user()->isAdmin())
|
||||||
|
<x-responsive-nav-link href="/admin" :active="request()->routeIs('api-tokens.index')">{{ __('Admin Panel') }}</x-responsive-nav-link>
|
||||||
|
<x-responsive-nav-link href="/pulse" :active="request()->routeIs('api-tokens.index')">{{ __('Pulse Stats') }}</x-responsive-nav-link>
|
||||||
|
<x-responsive-nav-link href="/horizon" :active="request()->routeIs('api-tokens.index')">{{ __('Horizon Queue') }}</x-responsive-nav-link>
|
||||||
|
@endif
|
||||||
|
|
||||||
<form method="POST" action="{{ route('logout') }}" x-data>
|
<form method="POST" action="{{ route('logout') }}" x-data>
|
||||||
@csrf
|
@csrf
|
||||||
<x-responsive-nav-link href="{{ route('logout') }}" @click.prevent="$root.submit();" :active="request()->routeIs('logout')">{{ __('Log Out') }}</x-responsive-nav-link>
|
<x-responsive-nav-link href="{{ route('logout') }}" @click.prevent="$root.submit();" :active="request()->routeIs('logout')">{{ __('Log Out') }}</x-responsive-nav-link>
|
||||||
|
@ -1,35 +1,113 @@
|
|||||||
<x-app-layout>
|
<x-app-layout>
|
||||||
|
<div class="sm:-mt-12 mb-6 dark:bg-gray-800 dark:text-gray-100">
|
||||||
<div class="sm:-mt-12 dark:bg-gray-800 dark:text-gray-100">
|
|
||||||
<div>
|
<div>
|
||||||
<img class="h-32 w-full object-cover lg:h-48" src="{{ $user->cover_photo_url }}" alt="{{ $user->name }}">
|
<img src="{{ $user->cover_photo_url }}" alt="{{ __(':name\'s Cover Photo', ['name' => $user->name]) }}" class="h-32 w-full object-cover lg:h-48" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
<div class="-mt-12 sm:-mt-16 sm:flex sm:items-end sm:space-x-5">
|
<div class="sm:-mt-12 sm:flex sm:items-end sm:space-x-5">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<img class="h-24 w-24 rounded-full ring-4 ring-white dark:ring-gray-800 sm:h-32 sm:w-32" src="{{ $user->profile_photo_url }}" alt="{{ $user->name }}" />
|
<img src="{{ $user->profile_photo_url }}" alt="{{ __(':name\'s Profile Picture', ['name' => $user->name]) }}" class="h-24 w-24 rounded-full ring-4 ring-white dark:ring-gray-800 sm:h-32 sm:w-32" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6 sm:flex sm:min-w-0 sm:flex-1 sm:items-center sm:justify-end sm:space-x-6 sm:pb-1">
|
<div class="mt-8 sm:flex sm:min-w-0 sm:flex-1 sm:items-center sm:justify-end sm:space-x-4">
|
||||||
<div class="mt-6 min-w-0 flex-1 sm:hidden md:block">
|
<div class="min-w-0 flex-1">
|
||||||
<h1 class="truncate text-2xl font-bold text-gray-900 dark:text-gray-100">{{ $user->name }}</h1>
|
<h1 class="truncate text-2xl font-bold text-gray-900 dark:text-gray-100">{{ $user->name }}</h1>
|
||||||
|
<div>
|
||||||
|
{{ __('Member since') }}
|
||||||
|
<x-time :datetime="$user->created_at" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{--
|
@if (auth()->check() && auth()->user()->id !== $user->id)
|
||||||
<div class="mt-6 flex flex-col justify-stretch space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0">
|
{{-- Follow Buttons --}}
|
||||||
<button type="button" class="inline-flex justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">
|
<livewire:user.follow-buttons :profile-user-id="$user->id" :is-following="auth()->user()->isFollowing($user->id)" />
|
||||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400 dark:text-gray-300" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path d="M3 4a2 2 0 00-2 2v1.161l8.441 4.221a1.25 1.25 0 001.118 0L19 7.162V6a2 2 0 00-2-2H3z" />
|
{{-- Message button --}}
|
||||||
<path d="M19 8.839l-7.77 3.885a2.75 2.75 0 01-2.46 0L1 8.839V14a2 2 0 002 2h14a2 2 0 002-2V8.839z" />
|
<x-profile-button>
|
||||||
</svg>
|
<x-slot:icon>
|
||||||
<span>Message</span>
|
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400 dark:text-gray-300" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
</button>
|
<path d="M3 4a2 2 0 00-2 2v1.161l8.441 4.221a1.25 1.25 0 001.118 0L19 7.162V6a2 2 0 00-2-2H3z" />
|
||||||
</div>
|
<path d="M19 8.839l-7.77 3.885a2.75 2.75 0 01-2.46 0L1 8.839V14a2 2 0 002 2h14a2 2 0 002-2V8.839z" />
|
||||||
--}}
|
</svg>
|
||||||
|
</x-slot:icon>
|
||||||
|
{{ __('Message') }}
|
||||||
|
</x-profile-button>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6 hidden min-w-0 flex-1 sm:block md:hidden">
|
|
||||||
<h1 class="truncate text-2xl font-bold text-gray-900 dark:text-gray-100">{{ $user->name }}</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mx-auto max-w-7xl px-2 sm:px-4 lg:px-8">
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||||
|
{{-- Mobile Follows --}}
|
||||||
|
<div class="lg:hidden flex flex-col justify-top items-center">
|
||||||
|
<x-user-follow-cards :profile-user-id="$user->id" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Left Column --}}
|
||||||
|
<div x-data="{ selectedTab: window.location.hash ? window.location.hash.substring(1) : 'wall' }" x-init="$watch('selectedTab', (tab) => {window.location.hash = tab})" class="lg:col-span-3 flex flex-col gap-6">
|
||||||
|
|
||||||
|
{{-- About --}}
|
||||||
|
@if ($user->about)
|
||||||
|
<div class="p-4 sm:p-6 bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 text-gray-800 dark:text-gray-200 drop-shadow-2xl">
|
||||||
|
{{ $user->about }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Tabs --}}
|
||||||
|
<div>
|
||||||
|
{{-- Mobile Dropdown --}}
|
||||||
|
<div class="sm:hidden">
|
||||||
|
<label for="tabs" class="sr-only">{{ __('Select a tab') }}</label>
|
||||||
|
<select id="tabs" name="tabs" x-model="selectedTab" class="block w-full rounded-md dark:text-white bg-gray-100 dark:bg-gray-950 border-gray-300 dark:border-gray-700 focus:border-grey-500 dark:focus:border-grey-600 focus:ring-grey-500 dark:focus:ring-grey-600">
|
||||||
|
<option value="wall">{{ __('Wall') }}</option>
|
||||||
|
<option value="mods">{{ __('Mods') }}</option>
|
||||||
|
<option value="activity">{{ __('Activity') }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Desktop Tabs --}}
|
||||||
|
<div class="hidden sm:block">
|
||||||
|
<nav class="isolate flex divide-x divide-gray-200 dark:divide-gray-800 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl" aria-label="Tabs">
|
||||||
|
<x-tab-button name="{{ __('Wall') }}" />
|
||||||
|
<x-tab-button name="{{ __('Mods') }}" />
|
||||||
|
<x-tab-button name="{{ __('Activity') }}" />
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Wall --}}
|
||||||
|
<div x-show="selectedTab === 'wall'" class="p-4 sm:p-6 bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 text-gray-800 dark:text-gray-200 drop-shadow-2xl">
|
||||||
|
<p>Not quite yet...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Mods --}}
|
||||||
|
<div x-show="selectedTab === 'mods'" class="">
|
||||||
|
@if($mods)
|
||||||
|
<div class="mb-4">
|
||||||
|
{{ $mods->links() }}
|
||||||
|
</div>
|
||||||
|
<div class="my-4 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||||
|
@foreach($mods as $mod)
|
||||||
|
<x-mod-card :mod="$mod" :version="$mod->latestVersion" />
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
{{ $mods->links() }}
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p>{{ __('This user has not yet published any mods.') }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Activity --}}
|
||||||
|
<div x-show="selectedTab === 'activity'" class="p-4 sm:p-6 bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 text-gray-800 dark:text-gray-200 drop-shadow-2xl">
|
||||||
|
<p>Not quite yet...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Desktop Follows --}}
|
||||||
|
<div class="max-lg:hidden flex flex-col justify-top items-center gap-6">
|
||||||
|
<x-user-follow-cards :profile-user-id="$user->id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</x-app-layout>
|
</x-app-layout>
|
||||||
|
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@ import defaultTheme from "tailwindcss/defaultTheme";
|
|||||||
import forms from "@tailwindcss/forms";
|
import forms from "@tailwindcss/forms";
|
||||||
import typography from "@tailwindcss/typography";
|
import typography from "@tailwindcss/typography";
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import("tailwindcss").Config} */
|
||||||
export default {
|
export default {
|
||||||
darkMode: "selector",
|
darkMode: "selector",
|
||||||
|
|
||||||
|
@ -184,22 +184,26 @@ it('handles the case where a dependent mod has no versions available', function
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles a large number of versions efficiently', function () {
|
it('handles a large number of versions efficiently', function () {
|
||||||
|
$startTime = microtime(true);
|
||||||
$versionCount = 100;
|
$versionCount = 100;
|
||||||
$modVersion = ModVersion::factory()->create();
|
|
||||||
|
|
||||||
$dependentMod = Mod::factory()->create();
|
$dependentMod = Mod::factory()->create();
|
||||||
for ($i = 0; $i < $versionCount; $i++) {
|
for ($i = 0; $i < $versionCount; $i++) {
|
||||||
ModVersion::factory()->recycle($dependentMod)->create(['version' => "1.0.$i"]);
|
ModVersion::factory()->recycle($dependentMod)->create(['version' => "1.0.$i"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a dependency with a broad constraint
|
// Create a mod and mod version, and then create a dependency for all versions of the dependent mod.
|
||||||
|
$modVersion = ModVersion::factory()->create();
|
||||||
ModDependency::factory()->recycle([$modVersion, $dependentMod])->create([
|
ModDependency::factory()->recycle([$modVersion, $dependentMod])->create([
|
||||||
'constraint' => '>=1.0.0',
|
'constraint' => '>=1.0.0',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Verify that all versions were resolved
|
$executionTime = microtime(true) - $startTime;
|
||||||
expect(ModResolvedDependency::where('mod_version_id', $modVersion->id)->count())->toBe($versionCount);
|
|
||||||
});
|
// Verify that all versions were resolved and that the execution time is reasonable.
|
||||||
|
expect(ModResolvedDependency::where('mod_version_id', $modVersion->id)->count())->toBe($versionCount)
|
||||||
|
->and($executionTime)->toBeLessThan(5); // Arbitrarily picked out of my ass.
|
||||||
|
})->skip('This is a performance test and is skipped by default. It will probably fail.');
|
||||||
|
|
||||||
it('calls DependencyVersionService when a Mod is updated', function () {
|
it('calls DependencyVersionService when a Mod is updated', function () {
|
||||||
$mod = Mod::factory()->create();
|
$mod = Mod::factory()->create();
|
||||||
|
99
tests/Feature/User/FollowTest.php
Normal file
99
tests/Feature/User/FollowTest.php
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
test('confirm a user cannot follow themself', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$user->follow($user);
|
||||||
|
|
||||||
|
$this->assertEmpty($user->follwers);
|
||||||
|
$this->assertEmpty($user->following);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirm a user can follow and unfollow another user', function () {
|
||||||
|
$user1 = User::factory()->create();
|
||||||
|
$user2 = User::factory()->create();
|
||||||
|
|
||||||
|
$user1->follow($user2);
|
||||||
|
|
||||||
|
$this->assertTrue($user1->isFollowing($user2));
|
||||||
|
|
||||||
|
$user1->unfollow($user2);
|
||||||
|
|
||||||
|
$this->assertFalse($user1->isFollowing($user2));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirm following a user cannot be done twice', function () {
|
||||||
|
$user1 = User::factory()->create();
|
||||||
|
$user2 = User::factory()->create();
|
||||||
|
|
||||||
|
$user1->follow($user2);
|
||||||
|
$user1->follow($user2);
|
||||||
|
|
||||||
|
$this->assertCount(1, $user1->following);
|
||||||
|
$this->assertCount(1, $user2->followers);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirm unfollowing a user that isnt being followed doesnt throw', function () {
|
||||||
|
$user1 = User::factory()->create();
|
||||||
|
$user2 = User::factory()->create();
|
||||||
|
|
||||||
|
$user1->unfollow($user2);
|
||||||
|
|
||||||
|
$this->assertEmpty($user1->following);
|
||||||
|
$this->assertEmpty($user2->followers);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirm unfollowing random number doesnt perform detach all', function () {
|
||||||
|
$user1 = User::factory()->create();
|
||||||
|
$user2 = User::factory()->create();
|
||||||
|
$user3 = User::factory()->create();
|
||||||
|
|
||||||
|
$user1->follow($user2);
|
||||||
|
$user1->follow($user3);
|
||||||
|
|
||||||
|
$this->assertTrue($user1->isFollowing($user2));
|
||||||
|
$this->assertTrue($user1->isFollowing($user3));
|
||||||
|
|
||||||
|
$this->assertCount(2, $user1->following);
|
||||||
|
$this->assertCount(1, $user2->followers);
|
||||||
|
$this->assertCount(1, $user3->followers);
|
||||||
|
|
||||||
|
$user1->unfollow(111112222233333);
|
||||||
|
|
||||||
|
$this->assertTrue($user1->isFollowing($user2));
|
||||||
|
$this->assertTrue($user1->isFollowing($user3));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirm null follow input fails', function () {
|
||||||
|
$this->expectException(TypeError::class);
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$user->follow(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirm empty follow input fails', function () {
|
||||||
|
$this->expectException(ArgumentCountError::class);
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$user->follow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirm null unfollow input fails', function () {
|
||||||
|
$this->expectException(TypeError::class);
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$user->unfollow(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('confirm empty unfollow input fails', function () {
|
||||||
|
$this->expectException(ArgumentCountError::class);
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$user->unfollow();
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user