This commit is contained in:
Marley Rae 2022-04-25 19:50:01 -07:00
parent 069aaacf11
commit ec631823b9
20 changed files with 471 additions and 185 deletions

View file

@ -17,9 +17,7 @@ public function __construct()
public function index()
{
$collective = auth_collective();
return view('admin.joined.index')->with([
'joined' => $collective->joined()->paginate(8),
]);
return view('admin.joined.index');
}
public function create()
@ -40,7 +38,13 @@ public function store(StoreJoinedRequest $request)
]);
Joined::store($validated);
return redirect()->route('joined.index')->with('success', 'Fanlisting added.');
return redirect()->route('admin.joined.index')->with('success', 'Fanlisting added.');
}
public function approve(Joined $joined)
{
$joined->approve();
return redirect()->route('admin.joined.index')->with('success', 'Fanlisting approved.');
}
public function show(Joined $joined)

View file

@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Admin;
use App\Models\Joined;
use Illuminate\Pagination\LengthAwarePaginator;
use Livewire\Component;
use Livewire\WithPagination;
class ListFanlistings extends Component
{
use WithPagination;
public string $class;
public function render()
{
if ($this->class == 'joined') {
$fanlistings = auth_collective()->joined()->paginate(8);
} else if ($this->class == 'owned') {
//
}
return view('livewire.admin.list-fanlistings', [
'fanlistings' => $fanlistings,
]);
}
}

View file

@ -2,7 +2,9 @@
namespace App\Http\Requests;
use App\Models\Joined;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class StoreJoinedRequest extends FormRequest
{
@ -13,7 +15,7 @@ class StoreJoinedRequest extends FormRequest
*/
public function authorize()
{
return true;
return $this->user()->can('create', Joined::class);
}
/**
@ -32,4 +34,4 @@ public function rules()
'approved' => ['nullable', 'boolean'],
];
}
}
}

0
artisan Normal file → Executable file
View file

View file

@ -9,13 +9,15 @@
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^9.2",
"laravel/sanctum": "^2.14.1",
"laravel/tinker": "^2.7"
"laravel/tinker": "^2.7",
"livewire/livewire": "^2.10"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
"laravel/sail": "^1.0.1",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^6.1",
"pestphp/pest-plugin-faker": "^1.0",
"pestphp/pest-plugin-laravel": "^1.2",
"phpunit/phpunit": "^9.5.10",
"spatie/laravel-ignition": "^1.0"

145
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "cb04fd9a53709c6f2ef827bfaf387e82",
"content-hash": "599c7b7d23bd5330b16dc91c44fd08af",
"packages": [
{
"name": "brick/math",
@ -1598,6 +1598,79 @@
],
"time": "2022-04-17T13:12:02+00:00"
},
{
"name": "livewire/livewire",
"version": "v2.10.5",
"source": {
"type": "git",
"url": "https://github.com/livewire/livewire.git",
"reference": "9ea6237760f627b3b6a05d15137880780ac843b5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/livewire/livewire/zipball/9ea6237760f627b3b6a05d15137880780ac843b5",
"reference": "9ea6237760f627b3b6a05d15137880780ac843b5",
"shasum": ""
},
"require": {
"illuminate/database": "^7.0|^8.0|^9.0",
"illuminate/support": "^7.0|^8.0|^9.0",
"illuminate/validation": "^7.0|^8.0|^9.0",
"league/mime-type-detection": "^1.9",
"php": "^7.2.5|^8.0",
"symfony/http-kernel": "^5.0|^6.0"
},
"require-dev": {
"calebporzio/sushi": "^2.1",
"laravel/framework": "^7.0|^8.0|^9.0",
"mockery/mockery": "^1.3.1",
"orchestra/testbench": "^5.0|^6.0|^7.0",
"orchestra/testbench-dusk": "^5.2|^6.0|^7.0",
"phpunit/phpunit": "^8.4|^9.0",
"psy/psysh": "@stable"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Livewire\\LivewireServiceProvider"
],
"aliases": {
"Livewire": "Livewire\\Livewire"
}
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Livewire\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Caleb Porzio",
"email": "calebporzio@gmail.com"
}
],
"description": "A front-end framework for Laravel.",
"support": {
"issues": "https://github.com/livewire/livewire/issues",
"source": "https://github.com/livewire/livewire/tree/v2.10.5"
},
"funding": [
{
"url": "https://github.com/livewire",
"type": "github"
}
],
"time": "2022-04-07T21:38:12+00:00"
},
{
"name": "monolog/monolog",
"version": "2.5.0",
@ -5857,6 +5930,76 @@
],
"time": "2021-01-03T15:53:42+00:00"
},
{
"name": "pestphp/pest-plugin-faker",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/pestphp/pest-plugin-faker.git",
"reference": "9d93419f1f47ffd856ee544317b2f9144a129044"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pestphp/pest-plugin-faker/zipball/9d93419f1f47ffd856ee544317b2f9144a129044",
"reference": "9d93419f1f47ffd856ee544317b2f9144a129044",
"shasum": ""
},
"require": {
"fakerphp/faker": "^1.9.1",
"pestphp/pest": "^1.0",
"php": "^7.3 || ^8.0"
},
"require-dev": {
"pestphp/pest-dev-tools": "dev-master"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"files": [
"src/Faker.php"
],
"psr-4": {
"Pest\\Faker\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "The Pest Faker Plugin",
"keywords": [
"faker",
"framework",
"pest",
"php",
"plugin",
"test",
"testing",
"unit"
],
"support": {
"source": "https://github.com/pestphp/pest-plugin-faker/tree/v1.0.0"
},
"funding": [
{
"url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L",
"type": "custom"
},
{
"url": "https://github.com/nunomaduro",
"type": "github"
},
{
"url": "https://www.patreon.com/nunomaduro",
"type": "patreon"
}
],
"time": "2021-01-03T15:42:35+00:00"
},
{
"name": "pestphp/pest-plugin-laravel",
"version": "v1.2.0",

View file

@ -1,42 +0,0 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}
/**
* Indicate that the model's email address should be unverified.
*
* @return static
*/
public function unverified()
{
return $this->state(function (array $attributes) {
return [
'email_verified_at' => null,
];
});
}
}

View file

@ -16,10 +16,9 @@ class JoinedSeeder extends Seeder
*/
public function run()
{
$cats = Category::all();
Joined::factory()
->count(50)
->hasAttached($cats->random())
->hasAttached(Category::inRandomOrder()->limit(rand(0,10)))
->create();
}
}

View file

@ -6,18 +6,20 @@ .form__input--file::-webkit-file-upload-button {
margin-right: 5px;
line-height: normal;
cursor: pointer;
text-decoration: none;
border-color: #7874ff;
background-color: #e4e3ff;
color: #7874ff;
-webkit-transition: background-color 0.4s, border-color 0.4s, color 0.4s;
transition: background-color 0.4s, border-color 0.4s, color 0.4s;
}
.pagination__item, .pagination__item--disabled, .pagination__item--active, .l-page-nav__link, .form__input--file::file-selector-button, .form__btn {
.pagination__item, .pagination__item--disabled, .pagination__item--active, .btn--table, .l-page-nav__link, .form__input--file::file-selector-button, .form__btn {
border: 1px solid #7874ff;
padding: 5px 10px;
margin-right: 5px;
line-height: normal;
cursor: pointer;
text-decoration: none;
border-color: #7874ff;
background-color: #e4e3ff;
color: #7874ff;
@ -30,7 +32,7 @@ .form__input--file:hover::-webkit-file-upload-button {
-webkit-transition: background-color 0.4s, border-color 0.4s, color 0.4s;
transition: background-color 0.4s, border-color 0.4s, color 0.4s;
}
.pagination__item:hover, .pagination__item--disabled:hover, .pagination__item--active:hover, .l-page-nav__link:hover, .form__input--file:hover::file-selector-button, .form__btn:hover {
.pagination__item:hover, .pagination__item--disabled:hover, .pagination__item--active:hover, .btn--table:hover, .l-page-nav__link:hover, .form__input--file:hover::file-selector-button, .form__btn:hover {
border-color: #de7cff;
background-color: #f8e5ff;
color: #de7cff;
@ -43,7 +45,7 @@ .form__input--file:focus::-webkit-file-upload-button {
-webkit-transition: background-color 0.4s, border-color 0.4s, color 0.4s;
transition: background-color 0.4s, border-color 0.4s, color 0.4s;
}
.pagination__item:focus, .pagination__item--disabled:focus, .pagination__item--active:focus, .l-page-nav__link:focus, .form__input--file:focus::file-selector-button, .form__btn:focus {
.pagination__item:focus, .pagination__item--disabled:focus, .pagination__item--active:focus, .btn--table:focus, .l-page-nav__link:focus, .form__input--file:focus::file-selector-button, .form__btn:focus {
border-color: #de7cff;
background-color: #f8e5ff;
color: #de7cff;
@ -360,6 +362,11 @@ .pagination__item:not(:last-child), .pagination__item--disabled:not(:last-child)
border-right: none;
}
/* ----------------------------------------------------------------------------------- &BTNS ---- */
.btn--table {
margin: 0;
}
.text--error {
color: #e20000;
}

View file

@ -36,4 +36,11 @@
.pagination__item--disabled, .pagination__item--active {
@extend .pagination__item;
@extend %disabled;
}
/* ----------------------------------------------------------------------------------- &BTNS ---- */
.btn--table {
@extend %btn;
margin: 0;
}

View file

@ -56,6 +56,7 @@ $c-red: #e20000;
margin-right: 5px;
line-height: normal;
cursor: pointer;
text-decoration: none;
@include hover;
}

View file

@ -7,37 +7,5 @@
@section('pg-title', 'Joined')
@section('content')
@inject('Category', 'App\Models\Category')
<table class="table">
<thead class="table__thead">
<tr>
<th>{{-- approved --}}</th>
<th>Subject</th> {{-- link --}}
<th>Categories</th>
<th>Image</th>
<th colspan="3">Actions</th>
</tr>
</thead>
<tbody class="table__tbody">
@foreach ($joined as $fl)
<tr>
<td>
@if (!$fl->approved)
<span class="text--error">&times;</span>
@endif
</td>
<td><a href="{{ $fl->url }}" target="_blank">{{ $fl->subject }}</a></td>
<td>{{ $fl->listCategories() }}</td>
<td><img src="{{ $fl->image }}"></td>
<td></td>
<td></td>
<td></td>
</tr>
@endforeach
</tbody>
</table>
{{ $joined->links() }}
<livewire:admin.list-fanlistings :class="'joined'" />
@endsection

View file

@ -8,6 +8,8 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css">
<link rel="stylesheet" href="/css/admin/style.css">
<livewire:styles />
</head>
<body>
<div class="l-container">
@ -54,5 +56,7 @@
</main>
</div>
<livewire:scripts />
</body>
</html>

View file

@ -0,0 +1,49 @@
<div>
<table class="table">
<thead class="table__thead">
<tr>
<th>{{-- approved --}}</th>
<th>Subject</th> {{-- link --}}
<th>Categories</th>
<th>Image</th>
<th colspan="3">Actions</th>
</tr>
</thead>
<tbody class="table__tbody">
@foreach ($fanlistings as $fl)
<tr>
<td>
@if (!$fl->approved)
<span class="text--error">&times;</span>
@endif
</td>
<td><a href="{{ $fl->url }}" target="_blank">{{ $fl->subject }}</a></td>
<td>{{ $fl->listCategories() }}</td>
<td><img src="{{ $fl->image }}"></td>
<td>
@if ($class == 'joined')
<a href="{{ route('admin.joined.approve', $fl) }}" class="btn--table">
Approve
</a>
@elseif ($class == 'owned')
<a href="#" class="btn--table">View</a>
@endif
</td>
<td>
<a href="{{ route("admin.$class.edit", $fl) }}" class="btn--table">Edit</a>
</td>
<td>
<x-admin.form.destroy :object="$fl" :route="'admin.'.$class.'.destroy'"
:btnClass="'btn--table'"/>
</td>
</tr>
@endforeach
</tbody>
</table>
{{ $fanlistings->links() }}
</div>

View file

@ -0,0 +1,31 @@
<div>
@if ($paginator->hasPages())
<nav class="pagination">
{{-- previous page --}}
<span>
@if ($paginator->onFirstPage())
<a class="pagination__previous pagination--disabled">
{!! __('pagination.previous') !!}
</a>
@else
<a class="pagnation__previous" wire:click="previousPage" wire:loading.attr="disabled">
{!! __('pagination.previous') !!}
</a>
@endif
</span>
{{-- next page --}}
<span>
@if ($paginator->hasMorePages())
<a class="pagination__next" wire:click="nextPage" wire:loading.attr="disabled">
{!! __('pagination.next') !!}
</a>
@else
<a class="pagination__next pagination--disabled">
{!! __('pagination.next') !!}
</a>
@endif
</span>
</nav>
@endif
</div>

View file

@ -0,0 +1,56 @@
<div>
@if ($paginator->hasPages())
@php(isset($this->numberOfPaginatorsRendered[$paginator->getPageName()]) ? $this->numberOfPaginatorsRendered[$paginator->getPageName()]++ : $this->numberOfPaginatorsRendered[$paginator->getPageName()] = 1)
<nav class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<span class="pagination__item--disabled">
&lt;
</span>
@else
<a class="pagination__item"
wire:click="previousPage('{{ $paginator->getPageName() }}')">
&lt;
</a>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<span class="pagination__item">{{ $element }}</span>
@endif
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<span class="pagination__item--disabled"
wire:key="paginator-{{ $paginator->getPageName() }}-{{ $this->numberOfPaginatorsRendered[$paginator->getPageName()] }}-page{{ $page }}">
{{ $page }}
</span>
@else
<a wire:click="gotoPage({{ $page }}, '{{ $paginator->getPageName() }}')"
class="pagination__item">
{{ $page }}
</a>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<a class="pagination__item"
wire:click="nextPage('{{ $paginator->getPageName() }}')">
&gt;
</a>
@else
<span class="pagination__item--disabled">
&gt;
</span>
@endif
</nav>
@endif
</div>

View file

@ -32,18 +32,20 @@
Route::get('/fanatic', [CollectiveController::class, 'dashboard'])->name('admin.dashboard');
Route::delete('/fanatic', [SessionsController::class, 'destroy'])->name('admin.sessions.destroy');
Route::get('/fanatic/joined', [JoinedController::class, 'index'])
Route::get('/fanatic/joined', [JoinedController::class, 'index'])
->name('admin.joined.index');
Route::get('/fanatic/joined/create', [JoinedController::class, 'create'])
Route::get('/fanatic/joined/create', [JoinedController::class, 'create'])
->name('admin.joined.create');
Route::post('/fanatic/joined', [JoinedController::class, 'store'])
Route::post('/fanatic/joined', [JoinedController::class, 'store'])
->name('admin.joined.store');
Route::get('/admin/joined/{joined}', [JoinedController::class, 'show'])
Route::patch('/fanatic/joined/{joined}/approve', [JoinedController::class, 'approve'])
->name('admin.joined.approve');
Route::get('/fanatic/joined/{joined}', [JoinedController::class, 'show'])
->name('admin.joined.show');
Route::get('/admin/joined/{joined}/edit', [JoinedController::class, 'edit'])
Route::get('/fanatic/joined/{joined}/edit', [JoinedController::class, 'edit'])
->name('admin.joined.edit');
Route::patch('/admin/joined/{joined}', [JoinedController::class, 'update'])
Route::patch('/fanatic/joined/{joined}', [JoinedController::class, 'update'])
->name('admin.joined.update');
Route::delete('/admin/joined/{joined}', [JoinedController::class, 'destroy'])
Route::delete('/fanatic/joined/{joined}', [JoinedController::class, 'destroy'])
->name('admin.joined.destroy');
});

View file

@ -0,0 +1,102 @@
<?php
use App\Models\Category;
use App\Models\Collective;
use function Pest\Faker\faker;
beforeEach(function () {
$this->user = Collective::first();
$this->request = [
'categories' => [rand(1,57), rand(1,57), rand(1,57)],
'url' => faker()->url,
'subject' => faker()->word,
'approved' => faker()->boolean,
];
});
it('gets joined create', function () {
$response = $this->actingAs($this->user)->get('fanatic/joined/create');
$response->assertViewIs('admin.joined.create');
});
it('does not show create to guests', function () {
$response = $this->get('/fanatic/joined/create');
$response->assertRedirect('/fanatic/login');
});
it('validates correct joined create form', function () {
$response = $this->actingAs($this->user)->post('/fanatic/joined', $this->request);
$response->assertValid();
});
it('fails missing categories', function () {
unset($this->request['categories']);
$response = $this->actingAs($this->user)->post('/fanatic/joined', $this->request);
$response->assertInvalid();
});
it('fails incorrect category format', function () {
$this->request['categories'] = rand(1,57);
$response = $this->actingAs($this->user)->post('/fanatic/joined', $this->request);
$response->assertInvalid();
});
it('fails incorrect category item format', function () {
$this->request['categories'][] = 'a';
$response = $this->actingAs($this->user)->post('/fanatic/joined', $this->request);
$response->assertInvalid();
});
it('fails non-existant category', function () {
$invalidCat = (Category::all()->count()) + 10;
$this->request['categories'][] = $invalidCat;
$response = $this->actingAs($this->user)->post('/fanatic/joined', $this->request);
$response->assertInvalid();
});
it('fails missing url', function () {
unset($this->request['url']);
$response = $this->actingAs($this->user)->post('/fanatic/joined', $this->request);
$response->assertInvalid();
});
it('fails incorrect url format', function () {
$this->request['url'] = faker()->word;
$response = $this->actingAs($this->user)->post('/fanatic/joined', $this->request);
$response->assertInvalid();
});
it('fails missing subject', function () {
unset($this->request['subject']);
$response = $this->actingAs($this->user)->post('/fanatic/joined', $this->request);
$response->assertInvalid();
});
it('fails non-string subject', function () {
$this->request['subject'] = 34950;
$response = $this->actingAs($this->user)->post('/fanatic/joined', $this->request);
$response->assertInvalid();
});
it('validates empty approved', function () {
unset($this->request['approved']);
$response = $this->actingAs($this->user)->post('/fanatic/joined', $this->request);
$response->assertValid();
});
it('fails non-bool approved', function () {
$this->request['approved'] = 'text';
$response = $this->actingAs($this->user)->post('/fanatic/joined', $this->request);
$response->assertInvalid();
});

View file

@ -0,0 +1,13 @@
<?php
use App\Models\Collective;
it('gets joined index', function () {
$response = $this->actingAs(Collective::first())->get('/fanatic/joined');
$response->assertViewIs('admin.joined.index');
});
it('does not show index to guests', function () {
$response = $this->get('/fanatic/joined');
$response->assertRedirect('/fanatic/login');
});

View file

@ -1,90 +0,0 @@
<?php
use App\Models\Collective;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
beforeEach(function () {
$this->user = Collective::first();
});
test('get joined index', function () {
$response = $this->actingAs($this->user)->get('fanatic/joined');
$response->assertViewIs('admin.joined.index');
});
test('get joined create', function () {
$response = $this->actingAs($this->user)->get('fanatic/joined/create');
$response->assertViewIs('admin.joined.create');
});
test('valid joined form passes create validation', function () {
$response = $this->actingAs($this->user)->post('/fanatic/joined', [
'categories' => [3, 10],
'url' => 'http://punkfairie.net',
'subject' => 'Test',
]);
$response->assertValid();
});
test('invalid category response does not pass create validation', function () {
$response = $this->actingAs($this->user)->post('/fanatic/joined', [
'categories' => 4,
'url' => 'http://punkfairie.net',
'subject' => 'Test',
]);
$response->assertInvalid();
});
test('invalid url does not pass create validation', function () {
$response = $this->actingAs($this->user)->post('/fanatic/joined', [
'categories' => [4],
'url' => 'hello',
'subject' => 'Test',
]);
$response->assertInvalid();
});
test('missing category does not pass create validation', function () {
$response = $this->actingAs($this->user)->post('/fanatic/joined', [
'url' => 'https://punkfairie.net',
'subject' => 'Test',
]);
$response->assertInvalid();
});
test('missing subject does not pass create validation', function () {
$response = $this->actingAs($this->user)->post('/fanatic/joined', [
'categories' => [1,5],
'url' => 'https://punkfairie.net',
]);
$response->assertInvalid();
});
test('missing url does not pass create validation', function () {
$response = $this->actingAs($this->user)->post('/fanatic/joined', [
'categories' => [2],
'subject' => 'Test',
]);
$response->assertInvalid();
});
// test('can upload joined button on create', function () {
// Storage::fake('local');
// $file = UploadedFile::fake()->image('code.png');
// $response = $this->actingAs($this->user)->post('/fanatic/joined', [
// 'categories' => [3, 10],
// 'url' => 'http://punkfairie.net',
// 'subject' => 'Test',
// 'image' => $file,
// ]);
// Storage::disk('local')->assertExists($file->hashName());
// });