commit
af508ded4b
24 changed files with 1753 additions and 23 deletions
|
@ -78,7 +78,7 @@ class UserController extends Controller
|
|||
"name" => "required|string|min:4|max:30",
|
||||
"pterodactyl_id" => "required|numeric|unique:users,pterodactyl_id,{$user->id}",
|
||||
"email" => "required|string|email",
|
||||
"credits" => "required|numeric|min:0|max:999999",
|
||||
"credits" => "required|numeric|min:0|max:99999999",
|
||||
"server_limit" => "required|numeric|min:0|max:1000000",
|
||||
"role" => Rule::in(['admin', 'mod', 'client', 'member']),
|
||||
]);
|
||||
|
|
196
app/Http/Controllers/Admin/VoucherController.php
Normal file
196
app/Http/Controllers/Admin/VoucherController.php
Normal file
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Voucher;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class VoucherController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return Application|Factory|View
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('admin.vouchers.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return Application|Factory|View
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('admin.vouchers.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'memo' => 'nullable|string|max:191',
|
||||
'code' => 'required|string|alpha_dash|max:36|min:4',
|
||||
'uses' => 'required|numeric|max:2147483647|min:1',
|
||||
'credits' => 'required|numeric|between:0,99999999',
|
||||
'expires_at' => ['nullable','date_format:d-m-Y','after:today',"before:10 years"],
|
||||
]);
|
||||
|
||||
Voucher::create($request->except('_token'));
|
||||
|
||||
return redirect()->route('admin.vouchers.index')->with('success', 'voucher has been created!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param Voucher $voucher
|
||||
* @return Response
|
||||
*/
|
||||
public function show(Voucher $voucher)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param Voucher $voucher
|
||||
* @return Application|Factory|View
|
||||
*/
|
||||
public function edit(Voucher $voucher)
|
||||
{
|
||||
return view('admin.vouchers.edit' , [
|
||||
'voucher' => $voucher
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Voucher $voucher
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function update(Request $request, Voucher $voucher)
|
||||
{
|
||||
$request->validate([
|
||||
'memo' => 'nullable|string|max:191',
|
||||
'code' => 'required|string|alpha_dash|max:36|min:4',
|
||||
'uses' => 'required|numeric|max:2147483647|min:1',
|
||||
'credits' => 'required|numeric|between:0,99999999',
|
||||
'expires_at' => ['nullable','date_format:d-m-Y','after:today',"before:10 years"],
|
||||
]);
|
||||
|
||||
$voucher->update($request->except('_token'));
|
||||
|
||||
return redirect()->route('admin.vouchers.index')->with('success', 'voucher has been updated!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param Voucher $voucher
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function destroy(Voucher $voucher)
|
||||
{
|
||||
$voucher->delete();
|
||||
return redirect()->back()->with('success', 'voucher has been removed!');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function redeem(Request $request)
|
||||
{
|
||||
#general validations
|
||||
$request->validate([
|
||||
'code' => 'required|exists:vouchers,code'
|
||||
]);
|
||||
|
||||
#get voucher by code
|
||||
$voucher = Voucher::where('code' , '=' , $request->input('code'))->firstOrFail();
|
||||
|
||||
#extra validations
|
||||
if ($voucher->getStatus() == 'USES_LIMIT_REACHED') throw ValidationException::withMessages([
|
||||
'code' => 'This voucher has reached the maximum amount of uses'
|
||||
]);
|
||||
|
||||
if ($voucher->getStatus() == 'EXPIRED') throw ValidationException::withMessages([
|
||||
'code' => 'This voucher has expired'
|
||||
]);
|
||||
|
||||
if (!$request->user()->vouchers()->where('id' , '=' , $voucher->id)->get()->isEmpty()) throw ValidationException::withMessages([
|
||||
'code' => 'You already redeemed this voucher code'
|
||||
]);
|
||||
|
||||
if ($request->user()->credits + $voucher->credits >= 99999999) throw ValidationException::withMessages([
|
||||
'code' => "You can't redeem this voucher because you would exceed the credit limit"
|
||||
]);
|
||||
|
||||
#redeem voucher
|
||||
$voucher->redeem($request->user());
|
||||
|
||||
return response()->json([
|
||||
'success' => "{$voucher->credits} credits have been added to your balance!"
|
||||
]);
|
||||
}
|
||||
|
||||
public function dataTable()
|
||||
{
|
||||
$query = Voucher::query();
|
||||
|
||||
return datatables($query)
|
||||
->addColumn('actions', function (Voucher $voucher) {
|
||||
return '
|
||||
<a data-content="Edit" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.vouchers.edit', $voucher->id) . '" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a>
|
||||
|
||||
<form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.vouchers.destroy', $voucher->id) . '">
|
||||
' . csrf_field() . '
|
||||
' . method_field("DELETE") . '
|
||||
<button data-content="Delete" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button>
|
||||
</form>
|
||||
';
|
||||
})
|
||||
->addColumn('status', function (Voucher $voucher) {
|
||||
$color = 'success';
|
||||
if ($voucher->getStatus() != 'VALID') $color = 'danger';
|
||||
return '<span class="badge badge-' . $color . '">' . $voucher->getStatus() . '</span>';
|
||||
})
|
||||
->editColumn('uses', function (Voucher $voucher) {
|
||||
$userCount = $voucher->users()->count();
|
||||
return "{$userCount} / {$voucher->uses}";
|
||||
})
|
||||
->editColumn('credits', function (Voucher $voucher) {
|
||||
return number_format($voucher->credits, 2, '.', '');
|
||||
})
|
||||
->editColumn('expires_at', function (Voucher $voucher) {
|
||||
if (!$voucher->expires_at) return "";
|
||||
return $voucher->expires_at ? $voucher->expires_at->diffForHumans() : '';
|
||||
})
|
||||
->editColumn('code', function (Voucher $voucher) {
|
||||
return "<code>{$voucher->code}</code>";
|
||||
})
|
||||
->rawColumns(['actions', 'code', 'status'])
|
||||
->make();
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers\Auth;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
|
@ -37,4 +38,34 @@ class LoginController extends Controller
|
|||
{
|
||||
$this->middleware('guest')->except('logout');
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
$this->username() => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'g-recaptcha-response' => ['required','recaptcha'],
|
||||
]);
|
||||
|
||||
// If the class is using the ThrottlesLogins trait, we can automatically throttle
|
||||
// the login attempts for this application. We'll key this by the username and
|
||||
// the IP address of the client making these requests into this application.
|
||||
if (method_exists($this, 'hasTooManyLoginAttempts') &&
|
||||
$this->hasTooManyLoginAttempts($request)) {
|
||||
$this->fireLockoutEvent($request);
|
||||
|
||||
return $this->sendLockoutResponse($request);
|
||||
}
|
||||
|
||||
if ($this->attemptLogin($request)) {
|
||||
return $this->sendLoginResponse($request);
|
||||
}
|
||||
|
||||
// If the login attempt was unsuccessful we will increment the number of attempts
|
||||
// to login and redirect the user back to the login form. Of course, when this
|
||||
// user surpasses their maximum number of attempts they will get locked out.
|
||||
$this->incrementLoginAttempts($request);
|
||||
|
||||
return $this->sendFailedLoginResponse($request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@ class ProfileController extends Controller
|
|||
return view('profile.index')->with([
|
||||
'user' => Auth::user(),
|
||||
'credits_reward_after_verify_discord' => Configuration::getValueByKey('CREDITS_REWARD_AFTER_VERIFY_DISCORD'),
|
||||
'discord_verify_command' => Configuration::getValueByKey('DISCORD_VERIFY_COMMAND')
|
||||
'force_email_verification' => Configuration::getValueByKey('FORCE_EMAIL_VERIFICATION'),
|
||||
'force_discord_verification' => Configuration::getValueByKey('FORCE_DISCORD_VERIFICATION'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,17 +7,30 @@ use App\Notifications\Auth\QueuedVerifyEmail;
|
|||
use App\Notifications\WelcomeMessage;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Spatie\Activitylog\Traits\CausesActivity;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
|
||||
/**
|
||||
* Class User
|
||||
* @package App\Models
|
||||
*/
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
use HasFactory, Notifiable, LogsActivity, CausesActivity;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected static $logAttributes = ['name', 'email'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected static $ignoreChangedAttributes = [
|
||||
'remember_token',
|
||||
'credits',
|
||||
|
@ -68,6 +81,9 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
'last_seen' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
@ -89,24 +105,38 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
}
|
||||
});
|
||||
|
||||
$user->vouchers()->detach();
|
||||
|
||||
Pterodactyl::client()->delete("/application/users/{$user->pterodactyl_id}");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function sendEmailVerificationNotification()
|
||||
{
|
||||
$this->notify(new QueuedVerifyEmail);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function credits()
|
||||
{
|
||||
return number_format($this->credits, 2, '.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAvatar(){
|
||||
return "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function creditUsage()
|
||||
{
|
||||
$usage = 0;
|
||||
|
@ -118,6 +148,9 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
return number_format($usage, 2, '.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|string|string[]
|
||||
*/
|
||||
public function getVerifiedStatus(){
|
||||
$status = '';
|
||||
if ($this->hasVerifiedEmail()) $status .= 'email ';
|
||||
|
@ -126,15 +159,31 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function vouchers(){
|
||||
return $this->belongsToMany(Voucher::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasOne
|
||||
*/
|
||||
public function discordUser(){
|
||||
return $this->hasOne(DiscordUser::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany
|
||||
*/
|
||||
public function servers()
|
||||
{
|
||||
return $this->hasMany(Server::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany
|
||||
*/
|
||||
public function payments()
|
||||
{
|
||||
return $this->hasMany(Payment::class);
|
||||
|
|
98
app/Models/Voucher.php
Normal file
98
app/Models/Voucher.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
|
||||
/**
|
||||
* Class Voucher
|
||||
* @package App\Models
|
||||
*/
|
||||
class Voucher extends Model
|
||||
{
|
||||
use HasFactory, LogsActivity;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fillable = [
|
||||
'memo',
|
||||
'code',
|
||||
'credits',
|
||||
'uses',
|
||||
'expires_at',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'expires_at'
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::deleting(function (Voucher $voucher) {
|
||||
$voucher->users()->detach();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
if ($this->users()->count() >= $this->uses) return 'USES_LIMIT_REACHED';
|
||||
if (!is_null($this->expires_at)) {
|
||||
if ($this->expires_at->isPast()) return 'EXPIRED';
|
||||
}
|
||||
|
||||
return 'VALID';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return float
|
||||
* @throws Exception
|
||||
*/
|
||||
public function redeem(User $user)
|
||||
{
|
||||
try {
|
||||
$user->increment('credits', $this->credits);
|
||||
$this->users()->attach($user);
|
||||
$this->logRedeem($user);
|
||||
} catch (Exception $exception) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return $this->credits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return null
|
||||
*/
|
||||
private function logRedeem(User $user)
|
||||
{
|
||||
activity()
|
||||
->performedOn($this)
|
||||
->causedBy($user)
|
||||
->log('redeemed');
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
34
database/factories/VoucherFactory.php
Normal file
34
database/factories/VoucherFactory.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\voucher;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class VoucherFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = voucher::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'memo' => $this->faker->word(),
|
||||
'code' => Str::random(36),
|
||||
'credits' => $this->faker->numberBetween(100, 1000),
|
||||
'uses' => $this->faker->numberBetween(1, 1000),
|
||||
'expires_at' => now()->addDays($this->faker->numberBetween(1, 90))->format('d-m-Y')
|
||||
];
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateVouchersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('vouchers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code', 36)->unique();
|
||||
$table->string('memo')->nullable();
|
||||
$table->unsignedFloat('credits', 10);
|
||||
$table->unsignedInteger('uses')->default(1);
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('vouchers');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateUserVoucherTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('user_voucher', function (Blueprint $table) {
|
||||
$table->foreignId('user_id')->constrained();
|
||||
$table->foreignId('voucher_id')->constrained();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('user_voucher');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpdateCreditsToUsersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->unsignedFloat('credits', 10)->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->unsignedFloat('credits')->change();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -78,6 +78,9 @@
|
|||
@case('created')
|
||||
<small><i class="fas text-success fa-plus mr-2"></i></small>
|
||||
@break
|
||||
@case('redeemed')
|
||||
<small><i class="fas text-success fa-money-check-alt mr-2"></i></small>
|
||||
@break
|
||||
@case('deleted')
|
||||
<small><i class="fas text-danger fa-times mr-2"></i></small>
|
||||
@break
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
<div class="form-group">
|
||||
<label for="credits">Credits</label>
|
||||
<input value="{{$user->credits}}" id="credits" name="credits" step="any" min="0"
|
||||
max="1000000"
|
||||
max="99999999"
|
||||
type="number" class="form-control @error('credits') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('credits')
|
||||
|
|
181
resources/views/admin/vouchers/create.blade.php
Normal file
181
resources/views/admin/vouchers/create.blade.php
Normal file
|
@ -0,0 +1,181 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<!-- CONTENT HEADER -->
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1>Vouchers</h1>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb float-sm-right">
|
||||
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{route('admin.vouchers.index')}}">Vouchers</a></li>
|
||||
<li class="breadcrumb-item"><a class="text-muted" href="{{route('admin.products.create')}}">Create</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT HEADER -->
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-money-check-alt mr-2"></i>Voucher details
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{route('admin.vouchers.store')}}" method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="form-group">
|
||||
<label for="memo">Memo <i data-toggle="popover" data-trigger="hover" data-content="Only admins can see this" class="fas fa-info-circle"></i></label>
|
||||
<input value="{{old('memo')}}" placeholder="Summer break voucher" id="memo"
|
||||
name="memo" type="text"
|
||||
class="form-control @error('memo') is-invalid @enderror">
|
||||
@error('memo')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="credits">* Credits</label>
|
||||
<input value="{{old('credits')}}" placeholder="500" id="credits"
|
||||
name="credits" type="number" step="any" min="0"
|
||||
max="99999999"
|
||||
class="form-control @error('credits') is-invalid @enderror">
|
||||
@error('credits')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="code">* Code</label>
|
||||
<div class="input-group">
|
||||
<input value="{{old('code')}}" placeholder="SUMMER" id="code" name="code"
|
||||
type="text"
|
||||
class="form-control @error('code') is-invalid @enderror"
|
||||
required="required">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-info" onclick="setRandomCode()" type="button">
|
||||
Random
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@error('code')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="uses">* Uses<i data-toggle="popover" data-trigger="hover" data-content="A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher." class="fas fa-info-circle"></i></label>
|
||||
<div class="input-group">
|
||||
<input value="{{old('uses') ?? 1}}" id="uses" min="1" max="2147483647"
|
||||
name="uses" type="number"
|
||||
class="form-control @error('uses') is-invalid @enderror"
|
||||
required="required">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-info" onclick="setMaxUses()" type="button">Max
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@error('uses')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="expires_at">Expires at</label>
|
||||
<div class="input-group date" id="expires_at" data-target-input="nearest">
|
||||
<input value="{{old('expires_at')}}" name="expires_at" placeholder="dd-mm-yyyy" type="text" class="form-control @error('expires_at') is-invalid @enderror datetimepicker-input" data-target="#expires_at"/>
|
||||
<div class="input-group-append" data-target="#expires_at" data-toggle="datetimepicker">
|
||||
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
@error('expires_at')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group text-right">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<i class="fas"></i>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT -->
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
$('#expires_at').datetimepicker({
|
||||
format : 'DD-MM-yyyy',
|
||||
icons: {
|
||||
time: 'far fa-clock',
|
||||
date: 'far fa-calendar',
|
||||
up: 'fas fa-arrow-up',
|
||||
down: 'fas fa-arrow-down',
|
||||
previous: 'fas fa-chevron-left',
|
||||
next: 'fas fa-chevron-right',
|
||||
today: 'fas fa-calendar-check',
|
||||
clear: 'far fa-trash-alt',
|
||||
close: 'far fa-times-circle'
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
function setMaxUses() {
|
||||
let element = document.getElementById('uses')
|
||||
element.value = element.max;
|
||||
console.log(element.max)
|
||||
}
|
||||
|
||||
|
||||
function setRandomCode() {
|
||||
let element = document.getElementById('code')
|
||||
element.value = getRandomCode(36)
|
||||
}
|
||||
|
||||
function getRandomCode(length) {
|
||||
let result = '';
|
||||
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-';
|
||||
let charactersLength = characters.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() *
|
||||
charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@endsection
|
186
resources/views/admin/vouchers/edit.blade.php
Normal file
186
resources/views/admin/vouchers/edit.blade.php
Normal file
|
@ -0,0 +1,186 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<!-- CONTENT HEADER -->
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1>Vouchers</h1>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb float-sm-right">
|
||||
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{route('admin.vouchers.index')}}">Vouchers</a></li>
|
||||
<li class="breadcrumb-item"><a class="text-muted"
|
||||
href="{{route('admin.products.edit' , $voucher->id)}}">Edit</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT HEADER -->
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-money-check-alt mr-2"></i>Voucher details
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{route('admin.vouchers.update' , $voucher->id)}}" method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<div class="form-group">
|
||||
<label for="memo">Memo <i data-toggle="popover" data-trigger="hover"
|
||||
data-content="Only admins can see this"
|
||||
class="fas fa-info-circle"></i></label>
|
||||
<input value="{{ $voucher->memo }}" placeholder="Summer break voucher" id="memo"
|
||||
name="memo" type="text"
|
||||
class="form-control @error('memo') is-invalid @enderror">
|
||||
@error('memo')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="credits">Credits *</label>
|
||||
<input value="{{$voucher->credits}}" placeholder="500" id="credits"
|
||||
name="credits" type="number" step="any" min="0"
|
||||
max="99999999"
|
||||
class="form-control @error('credits') is-invalid @enderror">
|
||||
@error('credits')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="code">Code *</label>
|
||||
<div class="input-group">
|
||||
<input value="{{$voucher->code}}" placeholder="SUMMER" id="code" name="code"
|
||||
type="text"
|
||||
class="form-control @error('code') is-invalid @enderror"
|
||||
required="required">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-info" onclick="setRandomCode()" type="button">
|
||||
Random
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@error('code')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="uses">Uses * <i data-toggle="popover" data-trigger="hover"
|
||||
data-content="A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher."
|
||||
class="fas fa-info-circle"></i></label>
|
||||
<div class="input-group">
|
||||
<input value="{{$voucher->uses}}" id="uses" min="1" max="2147483647"
|
||||
name="uses" type="number"
|
||||
class="form-control @error('uses') is-invalid @enderror"
|
||||
required="required">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-info" onclick="setMaxUses()" type="button">Max
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@error('uses')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="expires_at">Expires at</label>
|
||||
<div class="input-group date" id="expires_at" data-target-input="nearest">
|
||||
<input value="{{$voucher->expires_at ? $voucher->expires_at->format('d-m-Y') : ''}}" name="expires_at" placeholder="dd-mm-yyyy" type="text" class="form-control @error('expires_at') is-invalid @enderror datetimepicker-input" data-target="#expires_at"/>
|
||||
<div class="input-group-append" data-target="#expires_at" data-toggle="datetimepicker">
|
||||
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
@error('expires_at')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group text-right">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT -->
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
$('#expires_at').datetimepicker({
|
||||
format : 'DD-MM-yyyy',
|
||||
icons: {
|
||||
time: 'far fa-clock',
|
||||
date: 'far fa-calendar',
|
||||
up: 'fas fa-arrow-up',
|
||||
down: 'fas fa-arrow-down',
|
||||
previous: 'fas fa-chevron-left',
|
||||
next: 'fas fa-chevron-right',
|
||||
today: 'fas fa-calendar-check',
|
||||
clear: 'far fa-trash-alt',
|
||||
close: 'far fa-times-circle'
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
function setMaxUses() {
|
||||
let element = document.getElementById('uses')
|
||||
element.value = element.max;
|
||||
console.log(element.max)
|
||||
}
|
||||
|
||||
|
||||
function setRandomCode() {
|
||||
let element = document.getElementById('code')
|
||||
element.value = getRandomCode(36)
|
||||
}
|
||||
|
||||
function getRandomCode(length) {
|
||||
let result = '';
|
||||
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-';
|
||||
let charactersLength = characters.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() *
|
||||
charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@endsection
|
94
resources/views/admin/vouchers/index.blade.php
Normal file
94
resources/views/admin/vouchers/index.blade.php
Normal file
|
@ -0,0 +1,94 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<!-- CONTENT HEADER -->
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1>Vouchers</h1>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb float-sm-right">
|
||||
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a class="text-muted"
|
||||
href="{{route('admin.vouchers.index')}}">Vouchers</a></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT HEADER -->
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="card">
|
||||
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5 class="card-title"><i class="fas fa-money-check-alt mr-2"></i>Vouchers</h5>
|
||||
<a href="{{route('admin.vouchers.create')}}" class="btn btn-sm btn-primary"><i
|
||||
class="fas fa-plus mr-1"></i>Create new</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body table-responsive">
|
||||
|
||||
<table id="datatable" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Code</th>
|
||||
<th>Memo</th>
|
||||
<th>Credits</th>
|
||||
<th>Used / Uses</th>
|
||||
<th>Expires</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- END CUSTOM CONTENT -->
|
||||
|
||||
</section>
|
||||
<!-- END CONTENT -->
|
||||
|
||||
<script>
|
||||
function submitResult() {
|
||||
return confirm("Are you sure you wish to delete?") !== false;
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
$('#datatable').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
stateSave: true,
|
||||
ajax: "{{route('admin.vouchers.datatable')}}",
|
||||
columns: [
|
||||
{data: 'status'},
|
||||
{data: 'code'},
|
||||
{data: 'memo'},
|
||||
{data: 'credits'},
|
||||
{data: 'uses'},
|
||||
{data: 'expires_at'},
|
||||
{data: 'actions', sortable: false},
|
||||
],
|
||||
fnDrawCallback: function( oSettings ) {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@endsection
|
240
resources/views/admin/vouchers/show.blade.php
Normal file
240
resources/views/admin/vouchers/show.blade.php
Normal file
|
@ -0,0 +1,240 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<!-- CONTENT HEADER -->
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1>Products</h1>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb float-sm-right">
|
||||
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{route('admin.users.index')}}">Products</a></li>
|
||||
<li class="breadcrumb-item"><a class="text-muted"
|
||||
href="{{route('admin.products.show' , $product->id)}}">Show</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT HEADER -->
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between">
|
||||
<h5 class="card-title"><i class="fas fa-sliders-h mr-2"></i>Product</h5>
|
||||
<div class="ml-auto">
|
||||
<a data-content="Edit" data-trigger="hover" data-toggle="tooltip" href="{{ route('admin.products.edit', $product->id) }}" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a>
|
||||
<form class="d-inline" onsubmit="return submitResult();" method="post" action="{{ route('admin.products.destroy', $product->id) }}">
|
||||
{{ csrf_field() }}
|
||||
{{ method_field("DELETE") }}
|
||||
<button data-content="Delete" data-trigger="hover" data-toggle="tooltip" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>ID</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
{{$product->id}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>Name</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
{{$product->name}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>Price</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
<i class="fas fa-coins mr-1"></i>{{$product->price}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>Memory</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
{{$product->memory}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>CPU</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
{{$product->cpu}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>Swap</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
{{$product->swap}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>Disk</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
{{$product->disk}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>IO</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
{{$product->io}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>Databases</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
{{$product->databases}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>Allocations</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
{{$product->allocations}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>Created At</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
{{$product->created_at ? $product->created_at->diffForHumans() : ''}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>Description</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span class="d-inline-block text-truncate">
|
||||
{{$product->description}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<label>Updated At</label>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<span style="max-width: 250px;" class="d-inline-block text-truncate">
|
||||
{{$product->updated_at ? $product->updated_at->diffForHumans() : ''}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title"><i class="fas fa-server mr-2"></i>Servers</h5>
|
||||
</div>
|
||||
<div class="card-body table-responsive">
|
||||
|
||||
@include('admin.servers.table' , ['filter' => '?product=' . $product->id])
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- END CUSTOM CONTENT -->
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT -->
|
||||
|
||||
|
||||
|
||||
@endsection
|
|
@ -54,6 +54,15 @@
|
|||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
{!! htmlFormSnippet() !!}
|
||||
@error('g-recaptcha-response')
|
||||
<span class="text-danger" role="alert">
|
||||
<small><strong>{{ $message }}</strong></small>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<div class="icheck-primary">
|
||||
|
|
|
@ -117,6 +117,9 @@
|
|||
@case('created')
|
||||
<small><i class="fas text-success fa-plus mr-2"></i></small>
|
||||
@break
|
||||
@case('redeemed')
|
||||
<small><i class="fas text-success fa-money-check-alt mr-2"></i></small>
|
||||
@break
|
||||
@case('deleted')
|
||||
<small><i class="fas text-danger fa-times mr-2"></i></small>
|
||||
@break
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
{{-- summernote --}}
|
||||
<link rel="stylesheet" href="{{asset('plugins/summernote/summernote-bs4.min.css')}}">
|
||||
|
||||
{{-- datetimepicker --}}
|
||||
<link rel="stylesheet" href="{{asset('plugins/tempusdominus-bootstrap-4/css/tempusdominus-bootstrap-4.min.css')}}">
|
||||
|
||||
<link rel="stylesheet" href="{{asset('css/app.css')}}">
|
||||
<link rel="preload" href="{{asset('plugins/fontawesome-free/css/all.min.css')}}" as="style"
|
||||
onload="this.onload=null;this.rel='stylesheet'">
|
||||
|
@ -96,6 +99,11 @@
|
|||
Log back in
|
||||
</a>
|
||||
@endif
|
||||
<a class="dropdown-item" data-toggle="modal" data-target="#redeemVoucherModal"
|
||||
href="javascript:void(0)">
|
||||
<i class="fas fa-money-check-alt fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Redeem code
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<form method="post" action="{{route('logout')}}">
|
||||
@csrf
|
||||
|
@ -146,13 +154,15 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('store.index')}}"
|
||||
class="nav-link @if(Request::routeIs('store.*') || Request::routeIs('checkout')) active @endif">
|
||||
<i class="nav-icon fa fa-coins"></i>
|
||||
<p>Store</p>
|
||||
</a>
|
||||
</li>
|
||||
@if(env('PAYPAL_SECRET') && env('PAYPAL_CLIENT_ID') || env('APP_ENV', 'local') == 'local')
|
||||
<li class="nav-item">
|
||||
<a href="{{route('store.index')}}"
|
||||
class="nav-link @if(Request::routeIs('store.*') || Request::routeIs('checkout')) active @endif">
|
||||
<i class="nav-icon fa fa-coins"></i>
|
||||
<p>Store</p>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if(Auth::user()->role == 'admin')
|
||||
<li class="nav-header">Admin</li>
|
||||
|
@ -189,6 +199,14 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.vouchers.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.vouchers.*')) active @endif">
|
||||
<i class="nav-icon fas fa-money-check-alt"></i>
|
||||
<p>Vouchers</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-header">Pterodactyl</li>
|
||||
|
||||
<li class="nav-item">
|
||||
|
@ -227,6 +245,7 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="nav-header">Dashboard</li>
|
||||
|
||||
<li class="nav-item">
|
||||
|
@ -237,6 +256,16 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.usefullinks.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.usefullinks.*')) active @endif">
|
||||
<i class="nav-icon fas fa-link"></i>
|
||||
<p>Useful Links</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-header">Settings</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.configurations.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.configurations.*')) active @endif">
|
||||
|
@ -253,15 +282,6 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.usefullinks.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.usefullinks.*')) active @endif">
|
||||
<i class="nav-icon fas fa-link"></i>
|
||||
<p>Useful Links</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
@endif
|
||||
|
||||
</ul>
|
||||
|
@ -276,7 +296,7 @@
|
|||
<div class="content-wrapper">
|
||||
|
||||
@if(!Auth::user()->hasVerifiedEmail())
|
||||
@if(Auth::user()->created_at->diffInHours(now(), false) > 2)
|
||||
@if(Auth::user()->created_at->diffInHours(now(), false) > 1)
|
||||
<div class="alert alert-warning p-2 m-2">
|
||||
<h5><i class="icon fas fa-exclamation-circle"></i> Warning!</h5>
|
||||
You have not yet verified your email address <a class="text-primary"
|
||||
|
@ -289,6 +309,8 @@
|
|||
@endif
|
||||
|
||||
@yield('content')
|
||||
|
||||
@include('models.redeem_voucher_modal')
|
||||
</div>
|
||||
<!-- /.content-wrapper -->
|
||||
<footer class="main-footer">
|
||||
|
@ -316,9 +338,21 @@
|
|||
<!-- Summernote -->
|
||||
<script src="{{asset('plugins/summernote/summernote-bs4.min.js')}}"></script>
|
||||
|
||||
<!-- Moment.js -->
|
||||
<script src="{{asset('plugins/moment/moment.min.js')}}"></script>
|
||||
|
||||
<!-- Datetimepicker -->
|
||||
<script src="{{asset('plugins/tempusdominus-bootstrap-4/js/tempusdominus-bootstrap-4.min.js')}}"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
|
|
107
resources/views/models/redeem_voucher_modal.blade.php
Normal file
107
resources/views/models/redeem_voucher_modal.blade.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<!-- The Modal -->
|
||||
<div class="modal fade" id="redeemVoucherModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
||||
<!-- Modal Header -->
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Redeem voucher code</h4>
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Modal body -->
|
||||
<div class="modal-body">
|
||||
<form id="redeemVoucherForm" onsubmit="return false" method="post" action="{{route('voucher.redeem')}}">
|
||||
<div class="form-group">
|
||||
<label for="redeemVoucherCode">Code</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<i class="fas fa-money-check-alt"></i>
|
||||
</div>
|
||||
</div>
|
||||
<input id="redeemVoucherCode" name="code" placeholder="SUMMER" type="text"
|
||||
class="form-control">
|
||||
</div>
|
||||
<span id="redeemVoucherCodeError" class="text-danger"></span>
|
||||
<span id="redeemVoucherCodeSuccess" class="text-success"></span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Modal footer -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger" data-dismiss="modal">Close</button>
|
||||
<button name="submit" id="redeemVoucherSubmit" onclick="redeemVoucherCode()" type="button"
|
||||
class="btn btn-primary">Redeem
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
function redeemVoucherCode() {
|
||||
let form = document.getElementById('redeemVoucherForm')
|
||||
let button = document.getElementById('redeemVoucherSubmit')
|
||||
let input = document.getElementById('redeemVoucherCode')
|
||||
|
||||
console.log(form.method, form.action)
|
||||
button.disabled = true
|
||||
|
||||
$.ajax({
|
||||
method: form.method,
|
||||
url: form.action,
|
||||
dataType: 'json',
|
||||
data: {
|
||||
code: input.value
|
||||
},
|
||||
success: function (response) {
|
||||
resetForm()
|
||||
redeemVoucherSetSuccess(response)
|
||||
setTimeout(() => {
|
||||
$('#redeemVoucherModal').modal('toggle');
|
||||
} , 1500)
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
resetForm()
|
||||
redeemVoucherSetError(jqXHR)
|
||||
console.error(jqXHR.responseJSON)
|
||||
},
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
let button = document.getElementById('redeemVoucherSubmit')
|
||||
let input = document.getElementById('redeemVoucherCode')
|
||||
let successLabel = document.getElementById('redeemVoucherCodeSuccess')
|
||||
let errorLabel = document.getElementById('redeemVoucherCodeError')
|
||||
|
||||
input.classList.remove('is-invalid')
|
||||
input.classList.remove('is-valid')
|
||||
successLabel.innerHTML = ''
|
||||
errorLabel.innerHTML = ''
|
||||
button.disabled = false
|
||||
}
|
||||
|
||||
function redeemVoucherSetError(error) {
|
||||
let input = document.getElementById('redeemVoucherCode')
|
||||
let errorLabel = document.getElementById('redeemVoucherCodeError')
|
||||
|
||||
input.classList.add("is-invalid")
|
||||
|
||||
errorLabel.innerHTML = error.status === 422 ? error.responseJSON.errors.code[0] : error.responseJSON.message
|
||||
}
|
||||
|
||||
function redeemVoucherSetSuccess(response) {
|
||||
let input = document.getElementById('redeemVoucherCode')
|
||||
let successLabel = document.getElementById('redeemVoucherCodeSuccess')
|
||||
|
||||
successLabel.innerHTML = response.success
|
||||
input.classList.remove('is-invalid')
|
||||
input.classList.add('is-valid')
|
||||
}
|
||||
</script>
|
|
@ -25,7 +25,33 @@
|
|||
<div class="container-fluid">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<div class="col-lg-12 px-0">
|
||||
@if(!Auth::user()->hasVerifiedEmail() && strtolower($force_email_verification) == 'true')
|
||||
<div class="alert alert-warning p-2 m-2">
|
||||
<h5><i class="icon fas fa-exclamation-circle"></i>Required Email verification!</h5>
|
||||
You have not yet verified your email address
|
||||
<a class="text-primary" href="{{route('verification.send')}}">Click here to resend
|
||||
verification email</a> <br>
|
||||
Please contact support If you didn't receive your verification email.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(is_null(Auth::user()->discordUser) && strtolower($force_discord_verification) == 'true')
|
||||
@if(!empty(env('DISCORD_CLIENT_ID')) && !empty(env('DISCORD_CLIENT_SECRET')))
|
||||
<div class="alert alert-warning p-2 m-2">
|
||||
<h5><i class="icon fas fa-exclamation-circle"></i>Required Discord verification!</h5>
|
||||
You have not yet verified your discord account
|
||||
<a class="text-primary" href="{{route('auth.redirect')}}">Login with discord</a> <br>
|
||||
Please contact support If you face any issues.
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-danger p-2 m-2">
|
||||
<h5><i class="icon fas fa-exclamation-circle"></i>Required Discord verification!</h5>
|
||||
Due to system settings you are required to verify your discord account! <br>
|
||||
It looks like this hasn't been set-up correctly! Please contact support.
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -50,7 +76,14 @@
|
|||
<div class="col d-flex flex-column flex-sm-row justify-content-between mb-3">
|
||||
<div class="text-center text-sm-left mb-2 mb-sm-0"><h4
|
||||
class="pt-sm-2 pb-1 mb-0 text-nowrap">{{$user->name}}</h4>
|
||||
<p class="mb-0">{{$user->email}}</p>
|
||||
<p class="mb-0">{{$user->email}}
|
||||
@if($user->hasVerifiedEmail())
|
||||
<i data-toggle="popover" data-trigger="hover" data-content="Verified" class="text-success fas fa-check-circle"></i>
|
||||
@else
|
||||
<i data-toggle="popover" data-trigger="hover" data-content="Not verified" class="text-danger fas fa-exclamation-circle"></i>
|
||||
@endif
|
||||
|
||||
</p>
|
||||
<div class="mt-1">
|
||||
<span class="badge badge-primary"><i class="fa fa-coins mr-2"></i>{{$user->Credits()}}</span>
|
||||
</div>
|
||||
|
|
|
@ -24,6 +24,12 @@
|
|||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="text-right mb-3">
|
||||
<button type="button" data-toggle="modal" data-target="#redeemVoucherModal" class="btn btn-primary">
|
||||
<i class="fas fa-money-check-alt mr-2"></i>Redeem code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if($isPaypalSetup && $products->count() > 0)
|
||||
|
||||
<div class="card">
|
||||
|
|
|
@ -12,15 +12,16 @@ use App\Http\Controllers\Admin\ServerController as AdminServerController;
|
|||
use App\Http\Controllers\Admin\SettingsController;
|
||||
use App\Http\Controllers\Admin\UsefulLinkController;
|
||||
use App\Http\Controllers\Admin\UserController;
|
||||
use App\Http\Controllers\Admin\VoucherController;
|
||||
use App\Http\Controllers\Auth\SocialiteController;
|
||||
use App\Http\Controllers\HomeController;
|
||||
use App\Http\Controllers\NotificationController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\ServerController;
|
||||
use App\Http\Controllers\StoreController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -65,6 +66,9 @@ Route::middleware('auth')->group(function () {
|
|||
Route::get('/auth/redirect', [SocialiteController::class, 'redirect'])->name('auth.redirect');
|
||||
Route::get('/auth/callback', [SocialiteController::class, 'callback'])->name('auth.callback');
|
||||
|
||||
#voucher redeem
|
||||
Route::post('/voucher/redeem', [VoucherController::class, 'redeem'])->middleware('throttle:5,1')->name('voucher.redeem');
|
||||
|
||||
#admin
|
||||
Route::prefix('admin')->name('admin.')->middleware('admin')->group(function () {
|
||||
|
||||
|
@ -110,6 +114,9 @@ Route::middleware('auth')->group(function () {
|
|||
Route::get('usefullinks/datatable', [UsefulLinkController::class, 'datatable'])->name('usefullinks.datatable');
|
||||
Route::resource('usefullinks', UsefulLinkController::class);
|
||||
|
||||
Route::get('vouchers/datatable', [VoucherController::class, 'datatable'])->name('vouchers.datatable');
|
||||
Route::resource('vouchers', VoucherController::class);
|
||||
|
||||
Route::get('api/datatable', [ApplicationApiController::class, 'datatable'])->name('api.datatable');
|
||||
Route::resource('api', ApplicationApiController::class)->parameters([
|
||||
'api' => 'applicationApi',
|
||||
|
|
318
tests/Feature/TestVouchersController.php
Normal file
318
tests/Feature/TestVouchersController.php
Normal file
|
@ -0,0 +1,318 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Voucher;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class TestUsefulLinksController
|
||||
* @package Tests\Feature
|
||||
*/
|
||||
class TestVouchersController extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
/**
|
||||
* @dataProvider accessibleRoutesDataProvider
|
||||
* @param string $method
|
||||
* @param string $route
|
||||
* @param int $expectedStatus
|
||||
*/
|
||||
function test_accessible_routes(string $method, string $route, int $expectedStatus)
|
||||
{
|
||||
Voucher::factory()->create([
|
||||
'id' => 1
|
||||
]);
|
||||
|
||||
$response = $this->actingAs(User::factory()->create([
|
||||
'role' => 'admin',
|
||||
'pterodactyl_id' => '1'
|
||||
]))->{$method}($route);
|
||||
|
||||
$response->assertStatus($expectedStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider VoucherDataProvider
|
||||
* @param array $dataSet
|
||||
* @param int $expectedCount
|
||||
* @param bool $assertValidationErrors
|
||||
*/
|
||||
function test_creating_vouchers(array $dataSet, int $expectedCount, bool $assertValidationErrors)
|
||||
{
|
||||
$response = $this->actingAs($this->getTestUser())->post(route('admin.vouchers.store'), $dataSet);
|
||||
|
||||
if ($assertValidationErrors) $response->assertSessionHasErrors();
|
||||
else $response->assertSessionHasNoErrors();
|
||||
|
||||
$response->assertRedirect();
|
||||
$this->assertDatabaseCount('vouchers', $expectedCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
private function getTestUser(): User
|
||||
{
|
||||
return User::factory()->create([
|
||||
'role' => 'admin',
|
||||
'pterodactyl_id' => '1'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider VoucherDataProvider
|
||||
* @param array $dataSet
|
||||
* @param int $expectedCount
|
||||
* @param bool $assertValidationErrors
|
||||
*/
|
||||
function test_updating_voucher(array $dataSet, int $expectedCount, bool $assertValidationErrors)
|
||||
{
|
||||
$voucher = Voucher::factory()->create([
|
||||
'id' => 1
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($this->getTestUser())->patch(route('admin.vouchers.update', $voucher->id), $dataSet);
|
||||
|
||||
if ($assertValidationErrors) $response->assertSessionHasErrors();
|
||||
else $response->assertSessionHasNoErrors();
|
||||
|
||||
$response->assertRedirect();
|
||||
$this->assertDatabaseCount('vouchers', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function test_deleting_vouchers()
|
||||
{
|
||||
$voucher = Voucher::factory()->create([
|
||||
'id' => 1
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($this->getTestUser())->delete(route('admin.vouchers.update', $voucher->id));
|
||||
|
||||
$response->assertRedirect();
|
||||
$this->assertDatabaseCount('vouchers', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function VoucherDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'Valid dataset 1' => [
|
||||
'dataSet' => [
|
||||
"memo" => "TESTING",
|
||||
"code" => Str::random(20),
|
||||
"credits" => 500,
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->addDay()->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 1,
|
||||
'assertValidationErrors' => false
|
||||
],
|
||||
'Valid dataset 2' => [
|
||||
'dataSet' => [
|
||||
"code" => Str::random(36),
|
||||
"credits" => 500,
|
||||
"uses" => 500,
|
||||
],
|
||||
'expectedCount' => 1,
|
||||
'assertValidationErrors' => false
|
||||
],
|
||||
'Valid dataset 3' => [
|
||||
'dataSet' => [
|
||||
"memo" => "TESTING",
|
||||
"code" => Str::random(4),
|
||||
"credits" => 1000000,
|
||||
"uses" => 1,
|
||||
"expires_at" => now()->addYears(6)->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 1,
|
||||
'assertValidationErrors' => false
|
||||
],
|
||||
'Invalid dataset (memo to long)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(20),
|
||||
"credits" => 500,
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->addDay()->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (code to short)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(1),
|
||||
"credits" => 500,
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->addDay()->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (code missing)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"credits" => 500,
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->addDay()->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (code to long)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(60),
|
||||
"credits" => 500,
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->addDay()->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (credits missing)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(1),
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->addDay()->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (0 credits)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(1),
|
||||
"credits" => 0,
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->addDay()->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (to many credits)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(1),
|
||||
"credits" => 99999999999,
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->addDay()->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (uses missing)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(1),
|
||||
"credits" => 99999999999,
|
||||
"expires_at" => now()->addDay()->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (0 uses)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(1),
|
||||
"credits" => 99999999999,
|
||||
"uses" => 0,
|
||||
"expires_at" => now()->addDay()->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (expires_at today)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(1),
|
||||
"credits" => 99999999999,
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (expires_at earlier)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(1),
|
||||
"credits" => 99999999999,
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->subDays(5)->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (expires_at to far)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(1),
|
||||
"credits" => 99999999999,
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->addYears(100)->format('d-m-Y'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (expires_at invalid format 1)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(1),
|
||||
"credits" => 99999999999,
|
||||
"uses" => 500,
|
||||
"expires_at" => now()->addYears(100)->format('Y-m-d'),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
'Invalid dataset (expires_at invalid value)' => [
|
||||
'dataSet' => [
|
||||
"memo" => Str::random(250),
|
||||
"code" => Str::random(1),
|
||||
"credits" => 99999999999,
|
||||
"uses" => 500,
|
||||
"expires_at" => Str::random(20),
|
||||
],
|
||||
'expectedCount' => 0,
|
||||
'assertValidationErrors' => true
|
||||
],
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function accessibleRoutesDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'index page' => [
|
||||
'method' => 'get',
|
||||
'route' => '/admin/vouchers',
|
||||
'expectedStatus' => 200
|
||||
],
|
||||
'Create page' => [
|
||||
'method' => 'get',
|
||||
'route' => '/admin/vouchers/create',
|
||||
'expectedStatus' => 200
|
||||
],
|
||||
'Edit page' => [
|
||||
'method' => 'get',
|
||||
'route' => '/admin/vouchers/1/edit',
|
||||
'expectedStatus' => 200
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue