@@ -1,6 +1,6 @@ | |||||
# Code Examples | # Code Examples | ||||
Some sample code taken from sites I've completed. They're written in | Some sample code taken from sites I've completed. They're written in | ||||
Javascript, PHP, and SCSS. The examples are incomplete and meant for | |||||
demonstrative purposes only as I'm not comfortable sharing complete code of a | |||||
live, potentialy profitable project. | |||||
Javascript, PHP, and SCSS. Some examples are incomplete and meant for | |||||
demonstrative purposes only as I'm not comfortable sharing complete code of some | |||||
live, potentialy profitable projects. | |||||
@@ -0,0 +1,300 @@ | |||||
<?php | |||||
namespace App\Console\Commands; | |||||
use Illuminate\Console\Command; | |||||
use Illuminate\Support\Facades\Http; | |||||
use App\Models\Service; | |||||
use App\Http\Controllers\Supply; | |||||
class ServicesInit extends Command | |||||
{ | |||||
/** | |||||
* The name and signature of the console command. | |||||
* | |||||
* @var string | |||||
*/ | |||||
protected $signature = 'services:init'; | |||||
/** | |||||
* The console command description. | |||||
* | |||||
* @var string | |||||
*/ | |||||
protected $description = 'Generate inital services'; | |||||
/** | |||||
* Create a new command instance. | |||||
* | |||||
* @return void | |||||
*/ | |||||
public function __construct() | |||||
{ | |||||
parent::__construct(); | |||||
} | |||||
public function handle() { | |||||
$this->youtube(); | |||||
$this->tiktok(); | |||||
$this->instagram(); | |||||
$this->twitter(); | |||||
} | |||||
protected function youtube() { | |||||
$s = new Service; | |||||
$s->name = 'Unique Views'; | |||||
$s->type = 'views'; | |||||
$s->site = 'youtube'; | |||||
$s->maximum = 500000; | |||||
$s->minimum = 1000; | |||||
$s->price = 750; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmworld( $s->id, 418); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
/* | |||||
$s = new Service; | |||||
$s->name = 'Language Targeted Views'; | |||||
$s->type = 'views'; | |||||
$s->site = 'youtube'; | |||||
$s->modifier = 'language'; | |||||
$s->maximum = 100000; | |||||
$s->minimum = 1000; | |||||
$s->price = 600; | |||||
$s->available = true; | |||||
$s->note = '{language: { | |||||
french: 2974, spanish: 2975, german: 2976, | |||||
}}'; | |||||
$s->save(); | |||||
$s = new Service; | |||||
$s->name = 'Location Targeted Views'; | |||||
$s->site = 'youtube'; | |||||
$s->modifier = 'location'; | |||||
$s->maximum = 100000; | |||||
$s->minimum = 1000; | |||||
$s->price = 650; | |||||
$s->note = '{locations: { | |||||
india: 2600, brazil: 2597, colombia: 2608, philippines: 2609, mexico: | |||||
2673, netherlands: 2771, russia: 2772, spain: 2773, hongkong: 3065, | |||||
israel: 3119, }}'; | |||||
$s->available = true; | |||||
$s->description = 'Real impressionss and profile visits'; | |||||
$s->save(); | |||||
*/ | |||||
$s = new Service; | |||||
$s->name = 'Likes'; | |||||
$s->site = 'youtube'; | |||||
$s->maximum = 100000; | |||||
$s->minimum = 50; | |||||
$s->price = 850; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmworld($s->id, 385); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
$s = new Service; | |||||
$s->name = 'Comments'; | |||||
$s->site = 'youtube'; | |||||
$s->maximum = 500000; | |||||
$s->minimum = 10; | |||||
$s->price = 7500; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 3726); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
} | |||||
protected function instagram() { | |||||
$s = new Service; | |||||
$s->name = 'Likes with Profile Visits'; | |||||
$s->type = 'likes'; | |||||
$s->site = 'instagram'; | |||||
$s->maximum = 500000; | |||||
$s->minimum = 1000; | |||||
$s->price = 700; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 2997); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
$s = new Service; | |||||
$s->name = 'Likes'; | |||||
$s->type = 'likes'; | |||||
$s->site = 'instagram'; | |||||
$s->maximum = 50000; | |||||
$s->minimum = 100; | |||||
$s->price = 50; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 3775); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
$s = new Service; | |||||
$s->name = 'Post Impressions'; | |||||
$s->type = 'impressions'; | |||||
$s->site = 'instagram'; | |||||
$s->maximum = 1000000; | |||||
$s->minimum = 100; | |||||
$s->price = 100; | |||||
$s->available = true; | |||||
$s->description = 'Post impressions and visits'; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 618); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
$s = new Service; | |||||
$s->name = 'Profile Visits'; | |||||
$s->type = 'profile'; | |||||
$s->site = 'instagram'; | |||||
$s->maximum = 500000; | |||||
$s->minimum = 100; | |||||
$s->price = 100; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 2997); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
$s = new Service; | |||||
$s->name = 'Post Views'; | |||||
$s->type = 'views'; | |||||
$s->site = 'instagram'; | |||||
$s->maximum = 10000000; | |||||
$s->minimum = 100; | |||||
$s->price = 50; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 2840); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
$s = new Service; | |||||
$s->name = 'Followers'; | |||||
$s->type = 'followers'; | |||||
$s->site = 'instagram'; | |||||
$s->maximum = 100000; | |||||
$s->minimum = 10; | |||||
$s->price = 410; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 3882); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
} | |||||
protected function twitter() { | |||||
$s = new Service; | |||||
$s->name = 'Video Views'; | |||||
$s->type = 'views'; | |||||
$s->site = 'twitter'; | |||||
$s->maximum = 1000000; | |||||
$s->minimum = 100; | |||||
$s->price = 90; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 287); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
$s = new Service; | |||||
$s->name = 'Impressions'; | |||||
$s->type = 'impressions'; | |||||
$s->site = 'twitter'; | |||||
$s->maximum = 1000000; | |||||
$s->minimum = 100; | |||||
$s->price = 700; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 288); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
$s = new Service; | |||||
$s->name = 'Followers'; | |||||
$s->type = 'followers'; | |||||
$s->site = 'twitter'; | |||||
$s->maximum = 10000; | |||||
$s->minimum = 10; | |||||
$s->price = 600; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 3820); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
} | |||||
protected function tiktok() { | |||||
$s = new Service; | |||||
$s->name = 'Views'; | |||||
$s->type = 'views'; | |||||
$s->site = 'tiktok'; | |||||
$s->maximum = 10000000; | |||||
$s->minimum = 100; | |||||
$s->price = 70; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 3826); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
$s = new Service; | |||||
$s->name = 'Likes'; | |||||
$s->type = 'likes'; | |||||
$s->site = 'tiktok'; | |||||
$s->maximum = 100000; | |||||
$s->minimum = 100; | |||||
$s->price = 400; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 3935); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
$s = new Service; | |||||
$s->name = 'Followers'; | |||||
$s->type = 'followers'; | |||||
$s->site = 'tiktok'; | |||||
$s->maximum = 100000; | |||||
$s->minimum = 20; | |||||
$s->price = 300; | |||||
$s->available = true; | |||||
$s->save(); | |||||
$sup = Supply::smmkings($s->id, 3934); | |||||
$s->primary_supplier = $sup->id; | |||||
$s->save(); | |||||
} | |||||
} | |||||
@@ -0,0 +1,41 @@ | |||||
<?php | |||||
namespace App\Console; | |||||
use Illuminate\Console\Scheduling\Schedule; | |||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel; | |||||
class Kernel extends ConsoleKernel | |||||
{ | |||||
/** | |||||
* The Artisan commands provided by your application. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $commands = [ | |||||
Commands\ServicesInit::class | |||||
]; | |||||
/** | |||||
* Define the application's command schedule. | |||||
* | |||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule | |||||
* @return void | |||||
*/ | |||||
protected function schedule(Schedule $schedule) | |||||
{ | |||||
// $schedule->command('inspire')->hourly(); | |||||
} | |||||
/** | |||||
* Register the commands for the application. | |||||
* | |||||
* @return void | |||||
*/ | |||||
protected function commands() | |||||
{ | |||||
$this->load(__DIR__.'/Commands'); | |||||
require base_path('routes/console.php'); | |||||
} | |||||
} |
@@ -0,0 +1,41 @@ | |||||
<?php | |||||
namespace App\Exceptions; | |||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; | |||||
use Throwable; | |||||
class Handler extends ExceptionHandler | |||||
{ | |||||
/** | |||||
* A list of the exception types that are not reported. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $dontReport = [ | |||||
// | |||||
]; | |||||
/** | |||||
* A list of the inputs that are never flashed for validation exceptions. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $dontFlash = [ | |||||
'current_password', | |||||
'password', | |||||
'password_confirmation', | |||||
]; | |||||
/** | |||||
* Register the exception handling callbacks for the application. | |||||
* | |||||
* @return void | |||||
*/ | |||||
public function register() | |||||
{ | |||||
$this->reportable(function (Throwable $e) { | |||||
// | |||||
}); | |||||
} | |||||
} |
@@ -0,0 +1,13 @@ | |||||
<?php | |||||
namespace App\Http\Controllers; | |||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; | |||||
use Illuminate\Foundation\Bus\DispatchesJobs; | |||||
use Illuminate\Foundation\Validation\ValidatesRequests; | |||||
use Illuminate\Routing\Controller as BaseController; | |||||
class Controller extends BaseController | |||||
{ | |||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests; | |||||
} |
@@ -0,0 +1,66 @@ | |||||
<?php | |||||
namespace App\Http\Controllers; | |||||
use Illuminate\Http\Request; | |||||
use App\Models\Order; | |||||
use App\Models\Service; | |||||
use Illuminate\Support\Facades\Auth; | |||||
use Illuminate\Support\Facades\Log; | |||||
class OrderController extends Controller | |||||
{ | |||||
//This should also reduce user's available credits | |||||
public function newOrder(Request $request) { | |||||
$user = Auth::user(); | |||||
$order = new Order; | |||||
$order->service_id = $request->service; | |||||
$order->user_id = Auth::user()->id; | |||||
$order->quantity = $request->quantity; | |||||
$order->url = $request->url; | |||||
$order->note = $request->note ?: ''; | |||||
$order->status = 'processing'; | |||||
if (!$this->validateOrder($request)){ | |||||
abort(422); | |||||
} | |||||
$cost = ceil($order->quantity*$order->service->price/1000); | |||||
$user->credits = $user->credits - $cost; | |||||
if ($cost > $user->credits) { | |||||
abort(520, 'Insufficient Credits'); | |||||
} | |||||
$user->save(); | |||||
$order->save(); | |||||
} | |||||
// Should probably check for other things like service availability | |||||
public function validateOrder($order) { | |||||
$service = Service::find($order->service); | |||||
if ($order->quantity < $service->minimum || | |||||
$order->quantity > $service->maximum) { | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
public function changeURL(Request $request) { | |||||
$validated = $request->validate([ | |||||
'order' => 'required', | |||||
'url' => 'required' | |||||
]); | |||||
$order = Order::find($request->order); | |||||
$user = Auth::user(); | |||||
if (!in_array($order->status, ['processing', 'error'])) { | |||||
abort(422); | |||||
} | |||||
$order->url = $request->url; | |||||
$order->save(); | |||||
} | |||||
} |
@@ -0,0 +1,14 @@ | |||||
<?php | |||||
namespace App\Http\Controllers; | |||||
use Illuminate\Http\Request; | |||||
use Illuminate\Support\Facades\Log; | |||||
use App\Models\Service; | |||||
class ServiceController extends Controller | |||||
{ | |||||
public function getServices(Request $request) { | |||||
return Service::all(); | |||||
} | |||||
} |
@@ -0,0 +1,169 @@ | |||||
<?php | |||||
namespace App\Http\Controllers; | |||||
use Illuminate\Http\Request; | |||||
use Illuminate\Support\Facades\Http; | |||||
use Illuminate\Support\Facades\Log; | |||||
use App\Models\Supplier; | |||||
use App\Models\Order; | |||||
class Supply extends Controller | |||||
{ | |||||
// Accepts a site name, service id, and integer/array of supplier rows to | |||||
// create | |||||
public static function fetch($site) { | |||||
return Http::post(config("services.$site.link"), ['key' => | |||||
config("services.$site.key"), 'action' => 'services']); | |||||
} | |||||
public static function smmkings($service_id, $id) { | |||||
$services = Http::post(config("services.smmkings.link"), ['key' => | |||||
config("services.smmkings.key"), 'action' => 'services'])->json(); | |||||
foreach ($services as $service) { | |||||
if ($service['service'] == $id){ | |||||
$s = new Supplier; | |||||
$s->service_id = $service_id; | |||||
$s->supplier = 'smmkings'; | |||||
$s->supplier_id = (int) $service['service']; | |||||
$s->supplier_name = $service['name']; | |||||
$s->min = (int) $service['min']; | |||||
$s->max = (int) $service['max']; | |||||
$s->cost = $service['rate'] * 100; | |||||
$s->save(); | |||||
return $s; | |||||
} | |||||
} | |||||
} | |||||
public static function smmworld($service_id, $id) { | |||||
$services = Http::post(config("services.smmworld.link"), ['key' => | |||||
config("services.smmworld.key"), 'action' => 'services']); | |||||
$services = $services['services']; | |||||
foreach ($services as $service) { | |||||
if ($service['id'] == $id){ | |||||
$s = new Supplier; | |||||
$s->service_id = $service_id; | |||||
$s->supplier = 'smmworld'; | |||||
$s->supplier_id = $service['id']; | |||||
$s->supplier_name = $service['name']; | |||||
$s->min = $service['min']; | |||||
$s->max = $service['max']; | |||||
$s->cost = $service['price_per_k'] * 100; | |||||
$s->save(); | |||||
return $s; | |||||
} | |||||
} | |||||
} | |||||
//Fulfills an order with only the primary supplier of it's service | |||||
public static function fulfill($order) { | |||||
if (gettype($order) == 'integer') { | |||||
$order = Order::find($order); | |||||
} | |||||
return Supply::orderPrimary($order); | |||||
} | |||||
//Fulfills an order with the first available supplier it finds. It returns | |||||
//false if none could be found and the refreshed order model otherwise. | |||||
public static function fulfillWithAny($order) { | |||||
if (gettype($order) == 'integer') { | |||||
$order = Order::find($order); | |||||
} | |||||
if (Supply::orderPrimary($order)) { | |||||
return $order->refresh(); | |||||
}; | |||||
$suppliers = $order->service->suppliers; | |||||
foreach ($suppliers as $s) { | |||||
switch ($s->supplier) { | |||||
case 'smmkings': | |||||
if (Supply::orderSmmkings($order, $s)){ | |||||
return $order->refresh(); | |||||
} | |||||
break; | |||||
case 'smmworld': | |||||
if (Supply::orderSmmkings($order, $s)){ | |||||
return $order->refresh(); | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
//Fulfills an order with only the primary supplier of it's service | |||||
public static function orderPrimary($order) { | |||||
if (gettype($order) == 'integer') { | |||||
$order = Order::find($order); | |||||
} | |||||
$primary = $order->service->primary; | |||||
switch ($primary->supplier) { | |||||
case 'smmkings': | |||||
return Supply::orderSmmkings($order, $primary); | |||||
break; | |||||
case 'smmworld': | |||||
return Supply::orderSmmworld($order, $primary); | |||||
break; | |||||
} | |||||
} | |||||
//Should check that cost is not greater than price and store order | |||||
//information if completed. return false/NULL if an error occured | |||||
public static function orderSmmkings($order, $supplier) { | |||||
if ($supplier->supplier != 'smmkings') { | |||||
return false; | |||||
} | |||||
//Check that the order is not already pending. Probably by calling | |||||
//another function. | |||||
//Check supplier cost here to make sure the request is a good idea | |||||
$response = Http::post(config("services.smmkings.link"), ['key' => | |||||
config("services.smmkings.key"), 'action' => 'add', | |||||
'service' => $supplier->supplier_id, 'link' => | |||||
$order->url, 'quantity' => $order->quantity])->json(); | |||||
//This should log the error for later reporting | |||||
if (array_key_exists('error', $response)){ | |||||
return; | |||||
} | |||||
//store order information here | |||||
$status = Http::post(config("services.smmkings.link"), ['key' => | |||||
config("services.smmkings.key"), 'action' => 'status', | |||||
'order' => $response['order']])->json(); | |||||
return $response; | |||||
} | |||||
public static function orderSmmworld($order, $supplier) { | |||||
if ($supplier->supplier != 'smmworld') { | |||||
return false; | |||||
} | |||||
$response = Http::post(config("services.smmworld.link"), ['key' => | |||||
config("services.smmworld.key"), 'action' => 'add', | |||||
'service' => $supplier->supplier_id, 'link' => $order->url, 'quantity' => | |||||
$order->quantity])->json(); | |||||
return $response; | |||||
} | |||||
} |
@@ -0,0 +1,38 @@ | |||||
<?php | |||||
namespace App\Http\Controllers; | |||||
use Illuminate\Http\Request; | |||||
use Illuminate\Support\Facades\Auth; | |||||
use Mail; | |||||
use Illuminate\Support\Facades\Log; | |||||
use App\Models; | |||||
use App\Mail\SupportTicket; | |||||
class Ticket extends Controller | |||||
{ | |||||
public function send(Request $request){ | |||||
$validated = $request->validate([ | |||||
'topic' => 'required', | |||||
'message' => 'required' | |||||
]); | |||||
$ticket = $this->create($request->topic); | |||||
Mail::to('donotreply@trendplays.com')->send(new | |||||
SupportTicket($ticket, $request->message)); | |||||
} | |||||
//Should probably have a minimum character restriction later | |||||
public function create(String $type){ | |||||
$ticket = new Models\Ticket; | |||||
$ticket->user_id = Auth::user()->id; | |||||
$ticket->type = $type; | |||||
$ticket->status = 'processing'; | |||||
$ticket->complete = false; | |||||
$ticket->save(); | |||||
return $ticket; | |||||
} | |||||
} |
@@ -0,0 +1,165 @@ | |||||
<?php | |||||
namespace App\Http\Controllers; | |||||
use Illuminate\Http\Request; | |||||
use App\Models\User; | |||||
use App\Models\Order; | |||||
use App\Models\Service; | |||||
use App\Mail\ChangeEmail; | |||||
use Illuminate\Support\Facades\Log; | |||||
use Illuminate\Support\Facades\Hash; | |||||
use Illuminate\Support\Facades\URL; | |||||
use Illuminate\Auth\Events\Registered; | |||||
use Illuminate\Support\Facades\Password; | |||||
use Illuminate\Support\Facades\Auth; | |||||
use Illuminate\Notifications\Messages\MailMessage; | |||||
use Mail; | |||||
use Stripe\Stripe; | |||||
use Stripe\Customer; | |||||
class UserController extends Controller | |||||
{ | |||||
public function create(Request $request) { | |||||
$validated = $request->validate([ | |||||
'name' => 'required|max:30', | |||||
'email' => 'required|email|unique:users|max:255', | |||||
'password' => 'required|confirmed | |||||
|min:8|regex:/[a-z]/|regex:/[A-Z]/|regex:/[0-9]/' | |||||
]); | |||||
$user = new User; | |||||
$user->name = $request->name; | |||||
$user->email = $request->email; | |||||
$user->role = "client"; | |||||
$user->active = true; | |||||
$user->password = Hash::make($request->password); | |||||
$user->save(); | |||||
Auth::login($user); | |||||
event(new Registered($user)); | |||||
} | |||||
public function forgotPassword(Request $request) { | |||||
$request->validate(['email' => 'required|email']); | |||||
$status = Password::sendResetLink( | |||||
$request->only('email') | |||||
); | |||||
} | |||||
public function resetPassword(Request $request) { | |||||
$request->validate([ | |||||
'token' => 'required', | |||||
'email' => 'required|email', | |||||
'password' => 'required|min:8|confirmed', | |||||
]); | |||||
$status = Password::reset( | |||||
$request->only('email', 'password', | |||||
'password_confirmation', 'token'), | |||||
function ($user, $password) use ($request) { | |||||
$user->forceFill([ | |||||
'password' => Hash::make($password) | |||||
])->setRememberToken(Str::random(60)); | |||||
}); | |||||
if ($status == Password::PASSWORD_RESET) { | |||||
return response()->json([ | |||||
"status" => "success" | |||||
]); | |||||
} | |||||
} | |||||
public function login(Request $request) { | |||||
$credentials = $request->only('email', 'password'); | |||||
//This should probably be changed to not return a page | |||||
if (Auth::attempt($credentials)) { | |||||
$request->session()->regenerate(); | |||||
$this->clearPaying(); | |||||
} else { | |||||
abort(401); | |||||
} | |||||
} | |||||
public function logout(Request $request) { | |||||
Auth::logout(); | |||||
$request->session()->invalidate(); | |||||
$request->session()->regenerateToken(); | |||||
return redirect('/'); | |||||
} | |||||
//It should have an orderBy clause to make sure the most recent are first | |||||
//This should limit non pending orders to 50. Should also return a json of all services | |||||
public function getOrders(Request $request) { | |||||
return Auth::user()->orders()->with('service')->withCasts(['updated_at' | |||||
=> 'datetime:d-m-Y'])->latest()->limit(100)->get(); | |||||
} | |||||
public function changeName(Request $request) { | |||||
$validated = $request->validate([ | |||||
'name' => 'required|max:30' | |||||
]); | |||||
$user = Auth::user(); | |||||
$user->name = $request->name; | |||||
$user->save(); | |||||
return $user; | |||||
} | |||||
public function changeEmail(Request $request) { | |||||
$validated = $request->validate([ | |||||
'email' => 'required|email|unique:users,email', | |||||
]); | |||||
$link = URL::temporarySignedRoute('reset-email', now()->addDays(30), | |||||
['user' => Auth::user()->id, 'email' => $request->email]); | |||||
Mail::to($request->email)->send(new ChangeEmail(Auth::user()->email, | |||||
$link)); | |||||
} | |||||
public function resetEmail(Request $request) { | |||||
if (! $request->hasValidSignature()) { | |||||
abort(401); | |||||
} | |||||
$validated = $request->validate([ | |||||
'email' => 'required|email|unique:users,email', | |||||
]); | |||||
if (! $validated) { | |||||
abort(401); | |||||
} | |||||
$user = User::find($request->user); | |||||
$user->email = $request->email; | |||||
$user->save(); | |||||
return view('email-changed'); | |||||
} | |||||
public function changePassword(Request $request) { | |||||
$validated = $request->validate([ | |||||
'current_password' => 'password', | |||||
'password' => 'required|confirmed|min:8|regex:/.*[a-z].*/|regex:/.*[A-Z].*/|regex:/.*[0-9].*/' | |||||
]); | |||||
$user = Auth::user(); | |||||
$user->password = Hash::make($request->password); | |||||
$user->save(); | |||||
} | |||||
public function clearPaying() { | |||||
$user = Auth::user(); | |||||
if ($user->paying) { | |||||
$user->paying = false; | |||||
$user->save(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,67 @@ | |||||
<?php | |||||
namespace App\Http; | |||||
use Illuminate\Foundation\Http\Kernel as HttpKernel; | |||||
class Kernel extends HttpKernel | |||||
{ | |||||
/** | |||||
* The application's global HTTP middleware stack. | |||||
* | |||||
* These middleware are run during every request to your application. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $middleware = [ | |||||
// \App\Http\Middleware\TrustHosts::class, | |||||
\App\Http\Middleware\TrustProxies::class, | |||||
\Fruitcake\Cors\HandleCors::class, | |||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class, | |||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, | |||||
\App\Http\Middleware\TrimStrings::class, | |||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, | |||||
]; | |||||
/** | |||||
* The application's route middleware groups. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $middlewareGroups = [ | |||||
'web' => [ | |||||
\App\Http\Middleware\EncryptCookies::class, | |||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, | |||||
\Illuminate\Session\Middleware\StartSession::class, | |||||
// \Illuminate\Session\Middleware\AuthenticateSession::class, | |||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class, | |||||
\App\Http\Middleware\VerifyCsrfToken::class, | |||||
\Illuminate\Routing\Middleware\SubstituteBindings::class, | |||||
], | |||||
'api' => [ | |||||
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, | |||||
'throttle:api', | |||||
\Illuminate\Routing\Middleware\SubstituteBindings::class, | |||||
], | |||||
]; | |||||
/** | |||||
* The application's route middleware. | |||||
* | |||||
* These middleware may be assigned to groups or used individually. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $routeMiddleware = [ | |||||
'auth' => \App\Http\Middleware\Authenticate::class, | |||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, | |||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, | |||||
'can' => \Illuminate\Auth\Middleware\Authorize::class, | |||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, | |||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, | |||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, | |||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, | |||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, | |||||
]; | |||||
} |
@@ -0,0 +1,21 @@ | |||||
<?php | |||||
namespace App\Http\Middleware; | |||||
use Illuminate\Auth\Middleware\Authenticate as Middleware; | |||||
class Authenticate extends Middleware | |||||
{ | |||||
/** | |||||
* Get the path the user should be redirected to when they are not authenticated. | |||||
* | |||||
* @param \Illuminate\Http\Request $request | |||||
* @return string|null | |||||
*/ | |||||
protected function redirectTo($request) | |||||
{ | |||||
if (! $request->expectsJson()) { | |||||
return route('login'); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
<?php | |||||
namespace App\Http\Middleware; | |||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware; | |||||
class EncryptCookies extends Middleware | |||||
{ | |||||
/** | |||||
* The names of the cookies that should not be encrypted. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $except = [ | |||||
// | |||||
]; | |||||
} |
@@ -0,0 +1,17 @@ | |||||
<?php | |||||
namespace App\Http\Middleware; | |||||
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware; | |||||
class PreventRequestsDuringMaintenance extends Middleware | |||||
{ | |||||
/** | |||||
* The URIs that should be reachable while maintenance mode is enabled. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $except = [ | |||||
// | |||||
]; | |||||
} |
@@ -0,0 +1,32 @@ | |||||
<?php | |||||
namespace App\Http\Middleware; | |||||
use App\Providers\RouteServiceProvider; | |||||
use Closure; | |||||
use Illuminate\Http\Request; | |||||
use Illuminate\Support\Facades\Auth; | |||||
class RedirectIfAuthenticated | |||||
{ | |||||
/** | |||||
* Handle an incoming request. | |||||
* | |||||
* @param \Illuminate\Http\Request $request | |||||
* @param \Closure $next | |||||
* @param string|null ...$guards | |||||
* @return mixed | |||||
*/ | |||||
public function handle(Request $request, Closure $next, ...$guards) | |||||
{ | |||||
$guards = empty($guards) ? [null] : $guards; | |||||
foreach ($guards as $guard) { | |||||
if (Auth::guard($guard)->check()) { | |||||
return redirect(RouteServiceProvider::HOME); | |||||
} | |||||
} | |||||
return $next($request); | |||||
} | |||||
} |
@@ -0,0 +1,19 @@ | |||||
<?php | |||||
namespace App\Http\Middleware; | |||||
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware; | |||||
class TrimStrings extends Middleware | |||||
{ | |||||
/** | |||||
* The names of the attributes that should not be trimmed. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $except = [ | |||||
'current_password', | |||||
'password', | |||||
'password_confirmation', | |||||
]; | |||||
} |
@@ -0,0 +1,20 @@ | |||||
<?php | |||||
namespace App\Http\Middleware; | |||||
use Illuminate\Http\Middleware\TrustHosts as Middleware; | |||||
class TrustHosts extends Middleware | |||||
{ | |||||
/** | |||||
* Get the host patterns that should be trusted. | |||||
* | |||||
* @return array | |||||
*/ | |||||
public function hosts() | |||||
{ | |||||
return [ | |||||
$this->allSubdomainsOfApplicationUrl(), | |||||
]; | |||||
} | |||||
} |
@@ -0,0 +1,23 @@ | |||||
<?php | |||||
namespace App\Http\Middleware; | |||||
use Fideloper\Proxy\TrustProxies as Middleware; | |||||
use Illuminate\Http\Request; | |||||
class TrustProxies extends Middleware | |||||
{ | |||||
/** | |||||
* The trusted proxies for this application. | |||||
* | |||||
* @var array|string|null | |||||
*/ | |||||
protected $proxies; | |||||
/** | |||||
* The headers that should be used to detect proxies. | |||||
* | |||||
* @var int | |||||
*/ | |||||
protected $headers = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_AWS_ELB; | |||||
} |
@@ -0,0 +1,17 @@ | |||||
<?php | |||||
namespace App\Http\Middleware; | |||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; | |||||
class VerifyCsrfToken extends Middleware | |||||
{ | |||||
/** | |||||
* The URIs that should be excluded from CSRF verification. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $except = [ | |||||
'hooks/*' | |||||
]; | |||||
} |
@@ -0,0 +1,39 @@ | |||||
<?php | |||||
namespace App\Mail; | |||||
use Illuminate\Bus\Queueable; | |||||
use Illuminate\Contracts\Queue\ShouldQueue; | |||||
use Illuminate\Mail\Mailable; | |||||
use Illuminate\Queue\SerializesModels; | |||||
class ChangeEmail extends Mailable | |||||
{ | |||||
use Queueable, SerializesModels; | |||||
/** | |||||
* Create a new message instance. | |||||
* | |||||
* @return void | |||||
*/ | |||||
public $current_email; | |||||
public $link; | |||||
public function __construct($current_email, $link) | |||||
{ | |||||
$this->current_email = $current_email; | |||||
$this->link = $link; | |||||
} | |||||
/** | |||||
* Build the message. | |||||
* | |||||
* @return $this | |||||
*/ | |||||
public function build() | |||||
{ | |||||
$this->view('change-email'); | |||||
$this->subject('Email change requested'); | |||||
return $this; | |||||
} | |||||
} |
@@ -0,0 +1,49 @@ | |||||
<?php | |||||
namespace App\Mail; | |||||
use Illuminate\Bus\Queueable; | |||||
use Illuminate\Contracts\Queue\ShouldQueue; | |||||
use Illuminate\Mail\Mailable; | |||||
use Illuminate\Queue\SerializesModels; | |||||
use Illuminate\Support\Facades\Log; | |||||
use App\Models\Ticket; | |||||
class SupportTicket extends Mailable | |||||
{ | |||||
use Queueable, SerializesModels; | |||||
/** | |||||
* Create a new message instance. | |||||
* | |||||
* @return void | |||||
*/ | |||||
public $name; | |||||
public $email; | |||||
public $user_message; | |||||
public $type; | |||||
public $id; | |||||
public function __construct(Ticket $ticket, String $user_message) | |||||
{ | |||||
$this->name = $ticket->user->name; | |||||
$this->email = $ticket->user->email; | |||||
$this->user_message = $user_message; | |||||
$this->type = $ticket->type; | |||||
$this->id = $ticket->id; | |||||
} | |||||
/** | |||||
* Build the message. | |||||
* | |||||
* @return $this | |||||
*/ | |||||
public function build() | |||||
{ | |||||
$this->view('support-ticket'); | |||||
$this->subject("Ticket: $this->id, $this->type"); | |||||
$this->replyTo($this->email); | |||||
return $this; | |||||
} | |||||
} |
@@ -0,0 +1,30 @@ | |||||
<?php | |||||
namespace App\Models; | |||||
use Illuminate\Database\Eloquent\Factories\HasFactory; | |||||
use Illuminate\Database\Eloquent\Model; | |||||
use App\Models\User; | |||||
use App\Models\Service; | |||||
class Order extends Model | |||||
{ | |||||
use HasFactory; | |||||
protected $fillable = [ | |||||
'quantity', 'url' | |||||
]; | |||||
public function service() { | |||||
return $this->belongsTo(Service::class); | |||||
} | |||||
public function user() { | |||||
return $this->belongsTo(User::class); | |||||
} | |||||
public function getServiceNameAttribute() { | |||||
return $this->service()->name(); | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
<?php | |||||
namespace App\Models; | |||||
use Illuminate\Database\Eloquent\Factories\HasFactory; | |||||
use Illuminate\Database\Eloquent\Model; | |||||
use App\Models\User; | |||||
class Referral extends Model | |||||
{ | |||||
use HasFactory; | |||||
public function users() { | |||||
return $this->hasMany(User::class); | |||||
} | |||||
} |
@@ -0,0 +1,30 @@ | |||||
<?php | |||||
namespace App\Models; | |||||
use Illuminate\Database\Eloquent\Factories\HasFactory; | |||||
use Illuminate\Database\Eloquent\Model; | |||||
use App\Models\Order; | |||||
use App\Models\Supplier; | |||||
class Service extends Model | |||||
{ | |||||
use HasFactory; | |||||
protected $fillable = [ | |||||
'name', 'type', 'site', 'available' | |||||
]; | |||||
public function order() { | |||||
return $this->hasMany(Order::class); | |||||
} | |||||
public function suppliers() { | |||||
return $this->hasMany(Supplier::class); | |||||
} | |||||
public function primary() { | |||||
return $this->belongsTo(Supplier::class, 'primary_supplier', 'id'); | |||||
} | |||||
} |
@@ -0,0 +1,18 @@ | |||||
<?php | |||||
namespace App\Models; | |||||
use Illuminate\Database\Eloquent\Factories\HasFactory; | |||||
use Illuminate\Database\Eloquent\Model; | |||||
use App\Models\Service; | |||||
class Supplier extends Model | |||||
{ | |||||
use HasFactory; | |||||
public function service() { | |||||
return $this->belongsTo(Service::class); | |||||
} | |||||
} | |||||
@@ -0,0 +1,16 @@ | |||||
<?php | |||||
namespace App\Models; | |||||
use Illuminate\Database\Eloquent\Factories\HasFactory; | |||||
use Illuminate\Database\Eloquent\Model; | |||||
use App\Models\User; | |||||
class Ticket extends Model | |||||
{ | |||||
use HasFactory; | |||||
public function user() { | |||||
return $this->belongsTo(User::class); | |||||
} | |||||
} |
@@ -0,0 +1,16 @@ | |||||
<?php | |||||
namespace App\Models; | |||||
use Illuminate\Database\Eloquent\Factories\HasFactory; | |||||
use Illuminate\Database\Eloquent\Model; | |||||
use App\Models\User; | |||||
class Transaction extends Model | |||||
{ | |||||
use HasFactory; | |||||
public function user() { | |||||
return $this->belongsTo(User::class); | |||||
} | |||||
} |
@@ -0,0 +1,60 @@ | |||||
<?php | |||||
namespace App\Models; | |||||
use Illuminate\Contracts\Auth\MustVerifyEmail; | |||||
use Illuminate\Database\Eloquent\Factories\HasFactory; | |||||
use Illuminate\Foundation\Auth\User as Authenticatable; | |||||
use Illuminate\Notifications\Notifiable; | |||||
use App\Models\Order; | |||||
use App\Models\Transaction; | |||||
use App\Models\Ticket; | |||||
use Laravel\Cashier\Billable; | |||||
class User extends Authenticatable implements MustVerifyEmail | |||||
{ | |||||
use HasFactory, Notifiable, Billable; | |||||
/** | |||||
* The attributes that are mass assignable. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $fillable = [ | |||||
'name', | |||||
'email', | |||||
'password', | |||||
]; | |||||
/** | |||||
* The attributes that should be hidden for arrays. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $hidden = [ | |||||
'password', | |||||
'remember_token', | |||||
]; | |||||
/** | |||||
* The attributes that should be cast to native types. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $casts = [ | |||||
'email_verified_at' => 'datetime', | |||||
]; | |||||
public function orders() { | |||||
return $this->hasMany(Order::class); | |||||
} | |||||
public function transactions() { | |||||
return $this->hasMany(Transaction::class); | |||||
} | |||||
public function tickets() { | |||||
return $this->hasMany(Ticket::class); | |||||
} | |||||
} |
@@ -0,0 +1,28 @@ | |||||
<?php | |||||
namespace App\Providers; | |||||
use Illuminate\Support\ServiceProvider; | |||||
class AppServiceProvider extends ServiceProvider | |||||
{ | |||||
/** | |||||
* Register any application services. | |||||
* | |||||
* @return void | |||||
*/ | |||||
public function register() | |||||
{ | |||||
// | |||||
} | |||||
/** | |||||
* Bootstrap any application services. | |||||
* | |||||
* @return void | |||||
*/ | |||||
public function boot() | |||||
{ | |||||
// | |||||
} | |||||
} |
@@ -0,0 +1,30 @@ | |||||
<?php | |||||
namespace App\Providers; | |||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; | |||||
use Illuminate\Support\Facades\Gate; | |||||
class AuthServiceProvider extends ServiceProvider | |||||
{ | |||||
/** | |||||
* The policy mappings for the application. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $policies = [ | |||||
// 'App\Models\Model' => 'App\Policies\ModelPolicy', | |||||
]; | |||||
/** | |||||
* Register any authentication / authorization services. | |||||
* | |||||
* @return void | |||||
*/ | |||||
public function boot() | |||||
{ | |||||
$this->registerPolicies(); | |||||
// | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
<?php | |||||
namespace App\Providers; | |||||
use Illuminate\Support\Facades\Broadcast; | |||||
use Illuminate\Support\ServiceProvider; | |||||
class BroadcastServiceProvider extends ServiceProvider | |||||
{ | |||||
/** | |||||
* Bootstrap any application services. | |||||
* | |||||
* @return void | |||||
*/ | |||||
public function boot() | |||||
{ | |||||
Broadcast::routes(); | |||||
require base_path('routes/channels.php'); | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
<?php | |||||
namespace App\Providers; | |||||
use Illuminate\Auth\Events\Registered; | |||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification; | |||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; | |||||
use Illuminate\Support\Facades\Event; | |||||
class EventServiceProvider extends ServiceProvider | |||||
{ | |||||
/** | |||||
* The event listener mappings for the application. | |||||
* | |||||
* @var array | |||||
*/ | |||||
protected $listen = [ | |||||
Registered::class => [ | |||||
SendEmailVerificationNotification::class, | |||||
], | |||||
]; | |||||
/** | |||||
* Register any events for your application. | |||||
* | |||||
* @return void | |||||
*/ | |||||
public function boot() | |||||
{ | |||||
// | |||||
} | |||||
} |
@@ -0,0 +1,63 @@ | |||||
<?php | |||||
namespace App\Providers; | |||||
use Illuminate\Cache\RateLimiting\Limit; | |||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; | |||||
use Illuminate\Http\Request; | |||||
use Illuminate\Support\Facades\RateLimiter; | |||||
use Illuminate\Support\Facades\Route; | |||||
class RouteServiceProvider extends ServiceProvider | |||||
{ | |||||
/** | |||||
* The path to the "home" route for your application. | |||||
* | |||||
* This is used by Laravel authentication to redirect users after login. | |||||
* | |||||
* @var string | |||||
*/ | |||||
public const HOME = '/home'; | |||||
/** | |||||
* The controller namespace for the application. | |||||
* | |||||
* When present, controller route declarations will automatically be prefixed with this namespace. | |||||
* | |||||
* @var string|null | |||||
*/ | |||||
// protected $namespace = 'App\\Http\\Controllers'; | |||||
/** | |||||
* Define your route model bindings, pattern filters, etc. | |||||
* | |||||
* @return void | |||||
*/ | |||||
public function boot() | |||||
{ | |||||
$this->configureRateLimiting(); | |||||
$this->routes(function () { | |||||
Route::prefix('api') | |||||
->middleware('api') | |||||
->namespace($this->namespace) | |||||
->group(base_path('routes/api.php')); | |||||
Route::middleware('web') | |||||
->namespace($this->namespace) | |||||
->group(base_path('routes/web.php')); | |||||
}); | |||||
} | |||||
/** | |||||
* Configure the rate limiters for the application. | |||||
* | |||||
* @return void | |||||
*/ | |||||
protected function configureRateLimiting() | |||||
{ | |||||
RateLimiter::for('api', function (Request $request) { | |||||
return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()); | |||||
}); | |||||
} | |||||
} |
@@ -0,0 +1 @@ | |||||
require('./bootstrap'); |
@@ -0,0 +1,28 @@ | |||||
window._ = require('lodash'); | |||||
/** | |||||
* We'll load the axios HTTP library which allows us to easily issue requests | |||||
* to our Laravel back-end. This library automatically handles sending the | |||||
* CSRF token as a header based on the value of the "XSRF" token cookie. | |||||
*/ | |||||
window.axios = require('axios'); | |||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; | |||||
/** | |||||
* Echo exposes an expressive API for subscribing to channels and listening | |||||
* for events that are broadcast by Laravel. Echo and event broadcasting | |||||
* allows your team to easily build robust real-time web applications. | |||||
*/ | |||||
// import Echo from 'laravel-echo'; | |||||
// window.Pusher = require('pusher-js'); | |||||
// window.Echo = new Echo({ | |||||
// broadcaster: 'pusher', | |||||
// key: process.env.MIX_PUSHER_APP_KEY, | |||||
// cluster: process.env.MIX_PUSHER_APP_CLUSTER, | |||||
// forceTLS: true | |||||
// }); |
@@ -0,0 +1,8 @@ | |||||
<template> | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" | |||||
fill="currentColor" class="bi bi-eye-fill" viewBox="0 0 16 16"> | |||||
<path d="M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z"/> | |||||
<path d="M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 | |||||
1 0 0-7 3.5 3.5 0 0 0 0 7z"/> | |||||
</svg> | |||||
</template> |
@@ -0,0 +1,67 @@ | |||||
<?xml version="1.0" encoding="iso-8859-1"?> | |||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |||||
viewBox="0 0 551.034 551.034" style="enable-background:new 0 0 551.034 551.034;" xml:space="preserve"> | |||||
<g> | |||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="275.517" y1="4.57" x2="275.517" y2="549.72" gradientTransform="matrix(1 0 0 -1 0 554)"> | |||||
<stop offset="0" style="stop-color:#E09B3D"/> | |||||
<stop offset="0.3" style="stop-color:#C74C4D"/> | |||||
<stop offset="0.6" style="stop-color:#C21975"/> | |||||
<stop offset="1" style="stop-color:#7024C4"/> | |||||
</linearGradient> | |||||
<path style="fill:url(#SVGID_1_);" d="M386.878,0H164.156C73.64,0,0,73.64,0,164.156v222.722 | |||||
c0,90.516,73.64,164.156,164.156,164.156h222.722c90.516,0,164.156-73.64,164.156-164.156V164.156 | |||||
C551.033,73.64,477.393,0,386.878,0z M495.6,386.878c0,60.045-48.677,108.722-108.722,108.722H164.156 | |||||
c-60.045,0-108.722-48.677-108.722-108.722V164.156c0-60.046,48.677-108.722,108.722-108.722h222.722 | |||||
c60.045,0,108.722,48.676,108.722,108.722L495.6,386.878L495.6,386.878z"/> | |||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="275.517" y1="4.57" x2="275.517" y2="549.72" gradientTransform="matrix(1 0 0 -1 0 554)"> | |||||
<stop offset="0" style="stop-color:#E09B3D"/> | |||||
<stop offset="0.3" style="stop-color:#C74C4D"/> | |||||
<stop offset="0.6" style="stop-color:#C21975"/> | |||||
<stop offset="1" style="stop-color:#7024C4"/> | |||||
</linearGradient> | |||||
<path style="fill:url(#SVGID_2_);" d="M275.517,133C196.933,133,133,196.933,133,275.516s63.933,142.517,142.517,142.517 | |||||
S418.034,354.1,418.034,275.516S354.101,133,275.517,133z M275.517,362.6c-48.095,0-87.083-38.988-87.083-87.083 | |||||
s38.989-87.083,87.083-87.083c48.095,0,87.083,38.988,87.083,87.083C362.6,323.611,323.611,362.6,275.517,362.6z"/> | |||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="418.31" y1="4.57" x2="418.31" y2="549.72" gradientTransform="matrix(1 0 0 -1 0 554)"> | |||||
<stop offset="0" style="stop-color:#E09B3D"/> | |||||
<stop offset="0.3" style="stop-color:#C74C4D"/> | |||||
<stop offset="0.6" style="stop-color:#C21975"/> | |||||
<stop offset="1" style="stop-color:#7024C4"/> | |||||
</linearGradient> | |||||
<circle style="fill:url(#SVGID_3_);" cx="418.31" cy="134.07" r="34.15"/> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
</svg> |
@@ -0,0 +1,3 @@ | |||||
<template> | |||||
<svg class="loading-icon" data-set="loaders" data-loading="lazy" width="30px" height="30px" data-src="https://s2.svgbox.net/loaders.svg?ic=oval" data-icon="oval" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" color="" data-attributes-set="viewBox,xmlns,stroke,color" data-rendered="true"><g transform="translate(1 1)" stroke-width="2" fill="none" fill-rule="evenodd"><circle stroke-opacity=".5" cx="18" cy="18" r="18"></circle><path d="M36 18c0-9.94-8.06-18-18-18"><animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"></animateTransform></path></g></svg> | |||||
</template> |
@@ -0,0 +1,5 @@ | |||||
<template> | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-square-fill" viewBox="0 0 16 16"> | |||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z"/> | |||||
</svg> | |||||
</template> |
@@ -0,0 +1,6 @@ | |||||
<template> | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-square" viewBox="0 0 16 16"> | |||||
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/> | |||||
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/> | |||||
</svg> | |||||
</template> |
@@ -0,0 +1,39 @@ | |||||
<?xml version="1.0" encoding="iso-8859-1"?> | |||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | |||||
viewBox="0 0 461.001 461.001" style="enable-background:new 0 0 461.001 461.001;" xml:space="preserve"> | |||||
<path style="fill:#F61C0D;" d="M365.257,67.393H95.744C42.866,67.393,0,110.259,0,163.137v134.728 | |||||
c0,52.878,42.866,95.744,95.744,95.744h269.513c52.878,0,95.744-42.866,95.744-95.744V163.137 | |||||
C461.001,110.259,418.135,67.393,365.257,67.393z M300.506,237.056l-126.06,60.123c-3.359,1.602-7.239-0.847-7.239-4.568V168.607 | |||||
c0-3.774,3.982-6.22,7.348-4.514l126.06,63.881C304.363,229.873,304.298,235.248,300.506,237.056z"/> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
<g> | |||||
</g> | |||||
</svg> |
@@ -0,0 +1,116 @@ | |||||
import RegisterArea from './register-area/register-area.vue' | |||||
import Panel from './panel/panel.vue' | |||||
import '../scss/main.scss' | |||||
import { createApp } from 'vue' | |||||
importAll(require.context('../images', false, /\.(png|jpe?g|svg)$/)) | |||||
let heroText = document.querySelectorAll(".landing-hero h2,.landing-hero p") | |||||
let registerToggles = document.querySelectorAll(".register-btn, .register-area\ | |||||
.cancel-button, .services-cards button") | |||||
let token = getCookie('XSRF-TOKEN') | |||||
function importAll(r) { | |||||
return r.keys().map(r) | |||||
} | |||||
function getCookie(name) { | |||||
var re = new RegExp(name + "=([^;]+)") | |||||
var value = re.exec(document.cookie) | |||||
return (value != null) ? unescape(value[1]) : null | |||||
} | |||||
function getToken() { | |||||
return fetch("/sanctum/csrf-cookie", { | |||||
method: 'GET' | |||||
}).then( () => { | |||||
token = getCookie('XSRF-TOKEN') | |||||
return token | |||||
}) | |||||
} | |||||
function login(event) { | |||||
getToken().then(fetch("/login", { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': token}, | |||||
body: JSON.stringify({"email": | |||||
document.getElementById("login_email").value, | |||||
"password": document.getElementById("login_password").value}), | |||||
}).then(response => { | |||||
if (response.ok) { | |||||
window.location.assign("/panel") | |||||
} else { | |||||
document.querySelector("#login_form .error").innerText = | |||||
"Invalid credentials." | |||||
} | |||||
})) | |||||
event.preventDefault() | |||||
// event.stopPropogation() | |||||
} | |||||
//Attempt to resend the verification link | |||||
function resendLink(event) { | |||||
fetch("/resend-verification", { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': token}, | |||||
}).then(response => { | |||||
if (response.ok) { | |||||
event.target.parentNode.getElementsByTagName('h3')[0].innerText = | |||||
"The link has been resent." | |||||
} else { | |||||
event.target.parentNode.getElementsByTagName('h3')[0].innerText = | |||||
`${response.status} : ${response.statusText}` | |||||
}}) | |||||
event.preventDefault(); | |||||
} | |||||
function toggleNav() { | |||||
heroText.forEach(item => { | |||||
item.classList.toggle("hidden") | |||||
}) | |||||
document.querySelector("nav form.login").classList.toggle("active") | |||||
this.classList.toggle("toggled") | |||||
} | |||||
if (window.location.pathname == '/') { | |||||
document.getElementById('nav_toggle').addEventListener('click', toggleNav) | |||||
document.querySelector('#login_form button').addEventListener('click', login) | |||||
const app = createApp(RegisterArea).mount('#app') | |||||
// app.token = token | |||||
if (!token) {app.token = getToken()} | |||||
//Triggers for registration menu | |||||
for (let i = 0; i < registerToggles.length; i++) { | |||||
registerToggles[i].addEventListener("click", function() { | |||||
document.querySelector(".register-area").classList.add("active") | |||||
app.active = 'register' | |||||
}) | |||||
} | |||||
document.getElementById("forgot-password-btn").onclick = event => { | |||||
document.querySelector(".register-area").classList.add("active") | |||||
app.active = 'forgot' | |||||
event.preventDefault() | |||||
} | |||||
//FAQ collapsibles | |||||
let cols = document.getElementsByClassName("collapsible"); | |||||
for (let i = 0; i < cols.length; i++) { | |||||
cols[i].addEventListener("click", function() { | |||||
this.classList.toggle("active"); | |||||
}); | |||||
} | |||||
} else if (window.location.pathname == '/verify-email') { | |||||
document.getElementById('resend_verification').addEventListener("click", resendLink) | |||||
} else if (window.location.pathname == '/panel') { | |||||
const app = createApp(Panel).mount('#panel') | |||||
getToken().then(()=> {app.token = token}) | |||||
window.onhashchange = ()=>{app.active = location.hash} | |||||
} | |||||
@@ -0,0 +1,188 @@ | |||||
<template> | |||||
<section class="select-credits"> | |||||
<div class="credits-pane"><h2>10 Credits</h2> | |||||
<h3>$10.99</h3><div><span>Qty</span><input min="0" max="1000" v-model="packs.credits10" type="number"></div> | |||||
</div> | |||||
<div class="credits-pane"><div><h2>50 Credits</h2><span>+5 Free Credits</span></div> | |||||
<h3>$54.99 </h3><div><span>Qty</span><input min="0" max="1000" v-model="packs.credits50" type="number"></div> | |||||
</div> | |||||
<div class="credits-pane"><div><h2>100 Credits</h2><span>+10 Free Credits</span></div> | |||||
<h3>$109.99</h3> <div><span>Qty</span><input min="0" max="1000" v-model="packs.credits100" type="number"></div> | |||||
</div> | |||||
<div class="credits-pane"><div><h2>1000 Credits</h2><span>+150 Free Credits</span></div> | |||||
<h3>$1010.00</h3> <div><span>Qty</span><input min="0" max="1000" v-model="packs.credits1000" type="number"></div> | |||||
</div> | |||||
<h3>Total: ${{total.toLocaleString('en')}}</h3> | |||||
<div id="credits-errors"></div> | |||||
</section> | |||||
<section id="payment-section"> | |||||
<h4>Select a payment method</h4> | |||||
<div class="sliding-menu"> | |||||
<a @click="method = 'payeer'" :class="{selected: method == 'payeer'}">Payeer</a> | |||||
<a @click="method = 'pm'" :class="{selected: method == 'pm'}">Perfect Money</a> | |||||
<div :class="{right: (method == 'pm')}" class="menu-slider"><div></div></div> | |||||
</div> | |||||
<div v-if="method == 'payeer'" class="payment-window"> | |||||
<img src="../../images/payeer.png" alt=""> | |||||
<p>Payeer allows you to pay securely by transfering your choice of cryptocurrency | |||||
to a temporary address. | |||||
</p> | |||||
</div> | |||||
<div v-if="method == 'pm'" class="payment-window"> | |||||
<img src="../../images/perfect_money.svg" alt=""> | |||||
<p>Pay by transfering USD from your Perfect Money wallet.</p> | |||||
</div> | |||||
<div id="agreement-check"> | |||||
<input v-model="agreed" type="checkbox"><label>I have read and agree to the <a | |||||
href="/terms-and-policy">Terms and Policy</a> and will not pursue a dispute or chargeback.</label> | |||||
<div id="payment-error"></div> | |||||
</div> | |||||
</section> | |||||
<section class="credits-confirm"> | |||||
<button @click="pay" :disabled="!ready" | |||||
class="brand-btn">Buy<loading v-if="loading"></loading></button> | |||||
</section> | |||||
</template> | |||||
<script> | |||||
import Loading from '../icons/loading.vue' | |||||
function total() { | |||||
return this.packs.credits10*10.99 + this.packs.credits50*54.99 | |||||
+ this.packs.credits100*109.99 + this.packs.credits1000*1010 | |||||
} | |||||
//Gets the secret key specific to chosen payment amount and user | |||||
function getSecret() { | |||||
document.getElementById('credits-errors').textContent = '' | |||||
this.loading = true | |||||
return fetch('/panel/secret', { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({'packs': this.packs}) | |||||
}).then((response) => { | |||||
if (response.ok) { | |||||
return response.text() | |||||
} else { | |||||
document.getElementById('credits-errors').textContent = | |||||
`${response.status}: ${response.statusText}` | |||||
} | |||||
}).then(secret => { | |||||
this.loading = false | |||||
return secret | |||||
}) | |||||
} | |||||
function pay() { | |||||
if (this.method == 'payeer') { | |||||
this.payPayeer() | |||||
} else if (this.method == 'pm') { | |||||
this.payPm() | |||||
} | |||||
} | |||||
function makeInput(name, value) { | |||||
let input = document.createElement('input') | |||||
input.type = 'hidden' | |||||
input.name = name | |||||
input.value = value | |||||
return input | |||||
} | |||||
function payPayeer() { | |||||
fetch('/panel/payeer', { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({'packs': this.packs}) | |||||
}).then(response => {return response.json()}).then(data => { | |||||
let form = document.createElement('form') | |||||
document.body.appendChild(form) | |||||
form.method = 'POST' | |||||
form.action = 'https://payeer.com/merchant/' | |||||
form.appendChild(this.makeInput('m_shop', data.shop)) | |||||
form.appendChild(this.makeInput('m_orderid', data.transaction)) | |||||
form.appendChild(this.makeInput('m_amount', data.amount)) | |||||
form.appendChild(this.makeInput('m_curr', 'USD')) | |||||
form.appendChild(this.makeInput('m_desc', data.description)) | |||||
form.appendChild(this.makeInput('m_sign', data.signature)) | |||||
form.appendChild(this.makeInput('m_params', data.params)) | |||||
form.appendChild(this.makeInput('m_cipher_method', 'AES-256-CBC')) | |||||
form.submit() | |||||
/* console.log(data.signature) */ | |||||
}) | |||||
} | |||||
function payPm() { | |||||
fetch('/panel/pm', { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({'packs': this.packs}) | |||||
}).then(response => {return response.json()}).then(data => { | |||||
let form = document.createElement('form') | |||||
document.body.appendChild(form) | |||||
form.method = 'POST' | |||||
form.action = 'https://perfectmoney.is/api/step1.asp' | |||||
form.appendChild(this.makeInput('PAYEE_ACCOUNT', data.account)) | |||||
form.appendChild(this.makeInput('PAYEE_NAME', 'Trendplays Network')) | |||||
form.appendChild(this.makeInput('PAYMENT_AMOUNT', data.amount)) | |||||
form.appendChild(this.makeInput('PAYMENT_UNITS', 'USD')) | |||||
form.appendChild(this.makeInput('PAYMENT_ID', data.transaction)) | |||||
form.appendChild(this.makeInput('STATUS_URL', | |||||
'https://trendplays.com/hooks/pm-transaction')) | |||||
form.appendChild(this.makeInput('PAYMENT_URL', | |||||
'https://trendplays.com/panel/transaction-complete')) | |||||
form.appendChild(this.makeInput('PAYMENT_URL_METHOD', 'POST')) | |||||
form.appendChild(this.makeInput('NOPAYMENT_URL', | |||||
'https://trendplays.com/panel/transaction-failed')) | |||||
form.appendChild(this.makeInput('NOPAYMENT_URL_METHOD', 'GET')) | |||||
form.appendChild(this.makeInput('SUGGESTED_MEMO', data.description)) | |||||
form.appendChild(this.makeInput('SUGGESTED_MEMO_NOCHANGE', true)) | |||||
form.submit() | |||||
}) | |||||
} | |||||
function ready() { | |||||
if (this.packs.credis10 < 0) { | |||||
return false | |||||
} else if (this.packs.credis50 < 0) { | |||||
return false | |||||
} else if (this.packs.credis100 < 0) { | |||||
return false | |||||
} else if (this.packs.credis1000 < 0) { | |||||
return false | |||||
} | |||||
return this.total > 0 && !this.loading && this.agreed | |||||
} | |||||
export default { | |||||
components:{Loading}, | |||||
data() { | |||||
return {packs: {credits10: 0, credits50: 0, | |||||
credits100: 0, credits1000: 0}, loading: false, method: 'payeer', | |||||
agreed: false | |||||
} | |||||
}, | |||||
computed: {total, ready}, | |||||
methods: {getSecret, pay, payPm, payPayeer, makeInput}, | |||||
props: ['preferred', 'token'], | |||||
emits: ['purchaseComplete'], | |||||
} | |||||
</script> |
@@ -0,0 +1,82 @@ | |||||
<template> | |||||
<div v-if="cards && cards.length > 0"> | |||||
<div class="saved-cards-heading"> | |||||
<h5>Card</h5> <h5>Default</h5> <h5>Delete</h5> | |||||
</div> | |||||
<div v-for="card in cards" :key="card.id" class="saved-card"> | |||||
<span>{{card.card.brand[0].toUpperCase() + card.card.brand.substring(1)}} | |||||
(••••{{card.card.last4}})</span> | |||||
<span><input :checked="card.id == preferred" :value="card.id" name="selected-card" type="radio" | |||||
@change="change(card.id)"></span> | |||||
<span><img @click="remove(card.id)" src="../../images/close-icon-black.svg"/></span> | |||||
</div> | |||||
<p id="billing-error"></p> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
function get() { | |||||
fetch('/panel/cards', { | |||||
method: 'GET', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token} | |||||
}).then((response) => {response.json().then(data => { | |||||
this.cards = data.data | |||||
})}) | |||||
} | |||||
function change(card) { | |||||
fetch('/panel/change-card', { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({'card': card}) | |||||
}).then((response) => { | |||||
if (response.ok) { | |||||
response.json().then((data) => { | |||||
this.cards = data.data | |||||
}) | |||||
} else { | |||||
console.log('bad') | |||||
document.getElementById("billing-error").textContent = | |||||
`${response.status}: ${response.statusText}` | |||||
} | |||||
}) | |||||
} | |||||
function remove(card) { | |||||
if (card.length == 1) { | |||||
return | |||||
} | |||||
fetch('/panel/delete-card', { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({'card': card}) | |||||
}).then((response) => { | |||||
if (response.ok) { | |||||
response.json().then((data) => { | |||||
this.cards = data.data | |||||
}) | |||||
} else { | |||||
document.getElementById("billing-error").textContent = | |||||
`${response.status}: ${response.statusText}` | |||||
} | |||||
}) | |||||
} | |||||
export default { | |||||
data() { | |||||
return {cards: null} | |||||
}, | |||||
methods: {get, change, remove}, | |||||
created() { | |||||
this.get() | |||||
}, | |||||
props: ['token', 'preferred'], | |||||
} | |||||
</script> |
@@ -0,0 +1,71 @@ | |||||
<template> | |||||
<div id="overlay" v-if="selected"> | |||||
<img @click="$emit('close')" class="cancel icon" | |||||
src="../../images/cancel-icon2.svg" alt=""/> | |||||
<div class="overlay-item"> | |||||
<img v-if="selected.service.site == 'youtube'" class="icon" | |||||
src="../../images/youtube-icon.svg" alt=""/> | |||||
<img v-if="selected.service.site == 'instagram'" class="icon" | |||||
src="../../images/instagram-icon.svg" alt=""/> | |||||
<img v-if="selected.service.site == 'twitter'" class="icon" | |||||
src="../../images/twitter.svg" alt=""/> | |||||
<img v-if="selected.service.site == 'tiktok'" class="icon" | |||||
src="../../images/tik-tok.svg" alt=""/> | |||||
<h3>{{selected.service.name}}</h3> | |||||
<div class="details"> | |||||
<ul> | |||||
<li><b>Status:</b> <span>{{selected.status.charAt(0).toUpperCase() + | |||||
selected.status.slice(1)}}</span></li> | |||||
<li><b>Quantity:</b> <span>{{selected.quantity}}</span></li> | |||||
<li><b>Remaining:</b> <span>{{selected.remaining}}</span></li> | |||||
<li><b>URL:</b> <span>{{selected.url}}</span></li> | |||||
</ul> | |||||
</div> | |||||
<div v-if="selected.status == 'processing' || selected.status == | |||||
'error'" class="change-url"> | |||||
<h4>URL</h4> | |||||
<div><input v-model="url" type="url" id="url"></div> | |||||
<button @click="saveURL" :disabled="loading">Save | |||||
<loading-icon v-if="loading"></loading-icon></button> | |||||
<p id="overlay-error">{{errorMessage}}</p> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import LoadingIcon from '../icons/loading.vue' | |||||
function saveURL() { | |||||
fetch('/panel/save-url', { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({'url': this.url, 'order': this.selected.id}) | |||||
}).then(response => { | |||||
if (response.ok) { | |||||
this.errorMessage = 'Saved' | |||||
this.$emit('changeUrl', this.url) | |||||
} else { | |||||
this.errorMessage = 'An error occured' | |||||
} | |||||
}) | |||||
} | |||||
export default { | |||||
data() { | |||||
return {loading: false, errorMessage: '', url: this.selected.url} | |||||
}, | |||||
components: {LoadingIcon}, | |||||
methods: {saveURL}, | |||||
props: ['selected', 'token'], | |||||
emits: ['changeUrl', 'close'] | |||||
} | |||||
</script> |
@@ -0,0 +1,100 @@ | |||||
<template> | |||||
<div> | |||||
<section class="pending-pane"> | |||||
<div class="actions"><a class="new-order" href="#new-order">New</a><a | |||||
class="new-order" href="#credits">Add Credits</a></div> | |||||
<h4>Pending Orders</h4> | |||||
<ul> | |||||
<template v-bind:key='order.id' v-for="order in orders"> | |||||
<div class="pending-item" v-if="order.status == 'pending'"> | |||||
<div class="pending-heading"> | |||||
<li @click="togglePending($event)">{{order.service.name}} ({{order.updated_at}})</li> | |||||
<img class="chevron" src="../../images/chevron-down.svg" alt=""> | |||||
</div> | |||||
<div class="pending-content"> | |||||
<p>ID: {{order.id}}<br>URL: {{order.url}}<br>Quantity: | |||||
{{order.quantity}}<br>Note: {{order.note}}</p> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
</ul> | |||||
</section> | |||||
<div class="info-grey"><p>Orders are typically completed within 1-5 | |||||
days.</p><div></div></div> | |||||
<section class="history-pane"> | |||||
<h4>Order History</h4> | |||||
<div class="table-scroller"> | |||||
<table> | |||||
<thead><th>Date</th><th>ID</th><th>Name</th><th>Status</th> | |||||
<th>Quantity</th></thead> | |||||
<tbody> | |||||
<tr v-bind:key='order.id' v-for='order in | |||||
orders.slice(historyPage*10-10, historyPage*10)'> | |||||
<td>{{order.updated_at}}</td> | |||||
<td>{{order.id}}</td> | |||||
<td>{{order.service.name}}</td> | |||||
<td :class="order.status" | |||||
class="status"><span>{{order.status.charAt(0).toUpperCase() + | |||||
order.status.slice(1)}}</span></td> | |||||
<td>{{order.quantity}}</td> | |||||
<td> <eye @click="select(order)"></eye> </td> | |||||
</tr> | |||||
</tbody> | |||||
</table> | |||||
</div> | |||||
<img @click="moveHistory(false)" class="nav-btn left" | |||||
src="../../images/arrow-left-circle-fill.svg" alt=""/> | |||||
<p class="nav-legend">{{historyPage}}/{{Math.ceil(orders.length/10)}}</p> | |||||
<img @click="moveHistory(true)" class="nav-btn right" | |||||
src="../../images/arrow-right-circle-fill.svg" alt=""/> | |||||
</section> | |||||
<order-item v-if="selected" @close="close" :selected="selected" | |||||
:token="token" @change-url="(url) => selected.url = url"></order-item> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import Eye from '../icons/eye-fill.vue' | |||||
import OrderItem from './order-item.vue' | |||||
function togglePending(event) { | |||||
event.target.parentNode.parentNode.classList.toggle('selected') | |||||
} | |||||
function moveHistory(forward) { | |||||
if (forward) { | |||||
this.historyPage += 1 | |||||
} else { | |||||
this.historyPage -= 1 | |||||
} | |||||
if (this.historyPage < 1) { | |||||
this.historyPage = 1 | |||||
return | |||||
} else if (this.historyPage > this.orders.length/10+1) { | |||||
this.historyPage -= 1 | |||||
return | |||||
} | |||||
} | |||||
function close() { | |||||
this.selected = null | |||||
} | |||||
function select(order) { | |||||
this.selected = order | |||||
} | |||||
export default { | |||||
components: {Eye, OrderItem}, | |||||
data() {return {historyPage: 1, selected: null}}, | |||||
methods: { | |||||
togglePending, moveHistory, close, select | |||||
}, | |||||
props: ['orders', 'token'], | |||||
} | |||||
</script> |
@@ -0,0 +1,130 @@ | |||||
<template> | |||||
<template v-if="!loading"> | |||||
<sidebar :role="user.role" :active="active"></sidebar> | |||||
<transition name="fade" mode="out-in"> | |||||
<div v-if="active === ''" id="main"> | |||||
<section class="welcome-pane"><h3>Welcome, {{user.name}}!</h3></section> | |||||
<section class="credits-pane"><img src="../../images/coin-stack.svg" | |||||
alt="wallet" class="icon"/><p>Credits: | |||||
{{(user.credits/100).toLocaleString('en')}}</p></section> | |||||
<section class="alerts-pane"><h4>News and Announcements</h4> | |||||
<p>We've just launched. Thanks for joining us! Some features are still | |||||
being tested. If you experience a delay in credits being added to your | |||||
account, please wait 24 hours before contacting support@trendplays.com.</p> | |||||
</section> | |||||
<section class="recent-pane"><h4>Recent Activity</h4> | |||||
<table> | |||||
<thead><th>Date</th><th>Name</th><th>Status</th></thead> | |||||
<tbody> | |||||
<tr v-bind:key='order.id' v-for='order in orders.slice(0, 10)'> | |||||
<template v-if="order.status != 'pending'"> | |||||
<td>{{order.updated_at}}</td> | |||||
<td>{{order.service.name}}</td> | |||||
<td :class="order.status" | |||||
class="status"><span>{{order.status.charAt(0).toUpperCase() + | |||||
order.status.slice(1)}}</span></td> | |||||
</template> | |||||
</tr> | |||||
</tbody> | |||||
</table> | |||||
</section> | |||||
</div> | |||||
<past-orders :token="token" :orders="orders" v-else-if="active === '#orders'" | |||||
id="main"> | |||||
</past-orders> | |||||
<new-order :preferred="user.payment_method" :token="token" :active="active" | |||||
:credits="user.credits" v-else-if="active === | |||||
'#new-order' || active === '#credits'" id="main" @update-user='getUser' | |||||
@update-orders='getOrders'> | |||||
</new-order> | |||||
<div id="main" v-else-if="active === '#exit'"> | |||||
<section class="logout-pane"> | |||||
<h3>Are you sure you want to logout?</h3> | |||||
<a href="/logout">Logout</a> | |||||
</section> | |||||
</div> | |||||
<settings :token="token" :user="user" class="settings-page" id="main" | |||||
v-else-if="active === '#settings'"> | |||||
</settings> | |||||
<transaction-end @purchase-complete="getUser" :token="token" :user="user" | |||||
:active="active" v-else-if="active == | |||||
'#transaction-complete' || active == '#transaction-failed'"> | |||||
</transaction-end> | |||||
<support v-else-if="active == '#support'" :user="user" :token="token"></support> | |||||
</transition> | |||||
</template> | |||||
</template> | |||||
<script> | |||||
import Sidebar from './sidebar.vue' | |||||
import Settings from './settings.vue' | |||||
import PastOrders from './orders.vue' | |||||
import NewOrder from './services.vue' | |||||
import TransactionEnd from './transaction-endpoint.vue' | |||||
import Support from './support.vue' | |||||
function getServices() { | |||||
return fetch("/panel/services", { | |||||
method: 'GET', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token} | |||||
}).then(response => { | |||||
response.json().then(data => {this.services = data}) | |||||
}) | |||||
} | |||||
function getUser() { | |||||
return fetch("/panel/user", { | |||||
method: 'GET', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
}).then(response => { | |||||
return response.json() | |||||
}).then(data => { | |||||
this.user = data | |||||
}) | |||||
} | |||||
function getOrders() { | |||||
return fetch("/panel/orders", { | |||||
method: 'GET', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
}).then(response => { | |||||
return response.json() | |||||
}).then(data => { | |||||
this.orders = data | |||||
}) | |||||
} | |||||
export default { | |||||
components: { | |||||
Sidebar, Settings, PastOrders, NewOrder, | |||||
TransactionEnd, Support | |||||
}, | |||||
data() { | |||||
return {active: window.location.hash, user: null, | |||||
token: null, orders: null, loading: true,} | |||||
}, | |||||
methods: {getUser, getServices, getOrders, }, | |||||
created() { | |||||
let loaders = [] | |||||
loaders.push(this.getUser()) | |||||
loaders.push(this.getServices()) | |||||
loaders.push(this.getOrders()) | |||||
Promise.all(loaders).then(() => { | |||||
this.loading = false | |||||
}) | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,42 @@ | |||||
<template> | |||||
<form id="payment-form" action=""> | |||||
<label for="name">Name on Card</label> | |||||
<input @input="$emit('updateBillingName', $event)" id="billing-name" type="name"> | |||||
<div id="card-element"></div> | |||||
<div id="card-errors"></div> | |||||
<div id=save-card> | |||||
<input name="save-card" type="checkbox" checked="true"> | |||||
<label for="">Save Card</label> | |||||
</div> | |||||
</form> | |||||
</template> | |||||
<script> | |||||
function mountPaymentForm() { | |||||
let card = this.stripe.elements().create('card') | |||||
card.mount('#card-element') | |||||
this.$emit('setCard', card) | |||||
card.on('change', function(event) { | |||||
let displayError = document.getElementById('card-errors'); | |||||
if (event.error) { | |||||
displayError.textContent = event.error.message; | |||||
displayError.textContent = ''; | |||||
this.$emit('cardValid', true) | |||||
} else { | |||||
displayError.textContent = ''; | |||||
this.$emit('cardValid', true) | |||||
} | |||||
}.bind(this)); | |||||
} | |||||
export default { | |||||
data() { | |||||
return {billingName: null} | |||||
}, | |||||
methods: {mountPaymentForm}, | |||||
props: ['stripe'], | |||||
mounted: mountPaymentForm, | |||||
emits: ['updateBillingName', 'cardValid', 'setCard'] | |||||
} | |||||
</script> |
@@ -0,0 +1,17 @@ | |||||
<template> | |||||
<div class="services-menu"> | |||||
<a href="#new-order" :class="{selected: page == 'new-order'}">Services</a> | |||||
<a href="#credits" :class="{selected: page == 'credits'}">Credits</a> | |||||
<div :class="page" class="menu-slider"><div></div></div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return { | |||||
items: null | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,28 @@ | |||||
<template> | |||||
<div v-if="cards"> | |||||
<div v-for="(card, index) in cards" :key="card.id" class="saved-card"> | |||||
<span>{{card.card.brand[0].toUpperCase() + card.card.brand.substring(1)}} | |||||
(••••{{card.card.last4}})</span> | |||||
<input :checked="index === 0 || card.id == preferred" :value="card.id" name="selected-card" type="radio" | |||||
@change="$emit('update:pickedCard', card.id)"> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return {} | |||||
}, | |||||
mounted() { | |||||
if (this.cards && this.cards.length > 0) { | |||||
this.$emit('update:pickedCard', this.cards[0].id) | |||||
} | |||||
}, | |||||
unmounted() { | |||||
this.$emit('update:pickedCard', null) | |||||
}, | |||||
props: ['token', 'cards', 'preferred'], | |||||
emits: ['update:pickedCard'] | |||||
} | |||||
</script> |
@@ -0,0 +1,28 @@ | |||||
<template> | |||||
<section class="services-pane youtube" > | |||||
<h4>{{site.charAt(0).toUpperCase() + site.slice(1)}}</h4> | |||||
<ul :key="service.id" v-for="service in filter"> | |||||
<li v-if="service.available"><span>{{service.name}}</span><span>{{(service.price/100).toLocaleString('en')}}</span><span>{{service.minimum.toLocaleString('en')}}</span><span>{{service.maximum.toLocaleString('en')}}</span> | |||||
<svg @click="$emit('select', service)" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-square-fill" viewBox="0 0 16 16"> | |||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z"/> | |||||
</svg> | |||||
</li> | |||||
</ul> | |||||
</section> | |||||
</template> | |||||
<script> | |||||
function filter() { | |||||
if (!this.services || !this.site) {return} | |||||
return this.services.filter((s) => { | |||||
return s.site == this.site | |||||
}) | |||||
} | |||||
export default { | |||||
props: ['services', 'site'], | |||||
emits: ['select'], | |||||
computed: {filter} | |||||
} | |||||
</script> |
@@ -0,0 +1,196 @@ | |||||
<template> | |||||
<div> | |||||
<div class="sliding-menu"> | |||||
<a href="#new-order" :class="{selected: page == 'new-order'}">Services</a> | |||||
<a href="#credits" :class="{selected: page == 'credits'}">Credits</a> | |||||
<div :class="page" class="menu-slider"><div></div></div> | |||||
</div> | |||||
<h4 class="credits-display"><img class="icon" src="../../images/coin-stack.svg" alt=""><span> {{(credits/100).toLocaleString('en')}}</span></h4> | |||||
<template v-if="page == 'new-order'"> | |||||
<div class="services-legend"> | |||||
<h5>Name</h5><h5>Credits per 1000</h5><h5>Min Qt.</h5><h5>Max Qt.</h5> | |||||
</div> | |||||
<ServicePane :site="'youtube'" :services="services" | |||||
@select="select"></ServicePane> | |||||
<ServicePane :site="'instagram'" :services="services" | |||||
@select="select"></ServicePane> | |||||
<ServicePane :site="'twitter'" :services="services" | |||||
@select="select"></ServicePane> | |||||
<ServicePane :site="'tiktok'" :services="services" | |||||
@select="select"></ServicePane> | |||||
<div id="overlay" v-if="selected"> | |||||
<div v-if="!completed" class="overlay-item"> | |||||
<img @click="completed = false; selected = null" class="cancel icon" | |||||
src="../../images/cancel-icon2.svg" alt=""/> | |||||
<img v-if="selected.site == 'youtube'" class="icon" | |||||
src="../../images/youtube-icon.svg" alt=""/> | |||||
<img v-if="selected.site == 'instagram'" class="icon" | |||||
src="../../images/instagram-icon.svg" alt=""/> | |||||
<img v-if="selected.site == 'twitter'" class="icon" | |||||
src="../../images/twitter.svg" alt=""/> | |||||
<img v-if="selected.site == 'tiktok'" class="icon" | |||||
src="../../images/tik-tok.svg" alt=""/> | |||||
<h3>{{selected.name}}</h3> | |||||
<h4>Cost: {{(cost).toLocaleString('en')}}</h4> | |||||
<h4>Quantity</h4> | |||||
<div><input required :min="selected.minimum" :max="selected.maximum" | |||||
type="number" v-model="amount" id="selQty"><span> / | |||||
{{selected.maximum.toLocaleString('en')}}</span></div> | |||||
<template v-if="selected.modifier == 'location'"> | |||||
<h4>Location</h4> | |||||
<div><select required id="country" name=""> | |||||
<option value="usa">USA</option> | |||||
<option value="canada">Canada</option> | |||||
<option value="uk">United Kingdom</option> | |||||
<option value="germany">Germany</option> | |||||
<option value="france">France</option> | |||||
</select> | |||||
</div> | |||||
</template> | |||||
<template v-if="selected.modifier == 'language'"> | |||||
<h4>Location</h4> | |||||
<div><select required id="language" name=""> | |||||
<option value="english">English</option> | |||||
<option value="french">French</option> | |||||
<option value="spanish">Spanish</option> | |||||
<option value="german">German</option> | |||||
<option value="arabic">Arabic</option> | |||||
</select> | |||||
</div> | |||||
</template> | |||||
<h4>URL</h4> | |||||
<div><input required type="url" id="url" v-model="url"></div> | |||||
<button @click="buyService" :disabled="paying">Submit<loading | |||||
v-if="paying"></loading></button> | |||||
<p id="overlay-error">{{errorText}}</p> | |||||
</div> | |||||
<div class="overlay-item" v-else-if="completed"> | |||||
<img @click="completed = false; selected = null" class="cancel icon" | |||||
src="../../images/cancel-icon2.svg" alt=""/> | |||||
<img class="icon" src="../../images/checked2.svg" alt=""/> | |||||
<h3>Success!</h3> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<credits @purchase-complete="$emit('updateUser')" :preferred="preferred" :token="token" v-if="page == 'credits'"></credits> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import ServicePane from './service-pane.vue' | |||||
import Credits from './credits.vue' | |||||
import Loading from '../icons/loading.vue' | |||||
function select(service) { | |||||
this.completed = false | |||||
if (this.amount < service.minimum){ | |||||
this.amount = service.minimum; | |||||
} | |||||
if (this.amount > service.maximum){ | |||||
this.amount = service.maximum; | |||||
} | |||||
this.selected = service | |||||
this.errorText = '' | |||||
} | |||||
function cost() { | |||||
return (this.selected.price * this.amount / 100000).toFixed(2) | |||||
} | |||||
function buyService() { | |||||
if (!this.url) { | |||||
this.errorText = "You must provide a URL." | |||||
return | |||||
} else if (Math.ceil(this.cost > this.credits)) { | |||||
this.errorText = 'Insuficient Credits' | |||||
return | |||||
} else if (this.amount < this.selected.minimum || this.amount > this.selected.maximum) { | |||||
this.errorText = 'Invalid amount' | |||||
return | |||||
} | |||||
this.paying = true | |||||
let note = '' | |||||
let country = document.getElementById('country') | |||||
let language = document.getElementById('language') | |||||
if (country) { | |||||
note = JSON.stringify({'location': country.value}) | |||||
} else if (language) { | |||||
note = JSON.stringify({'language': language.value}) | |||||
} | |||||
fetch('/panel/orders', { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({'service': this.selected.id, | |||||
'quantity': this.amount, 'url': this.url, 'note': note}), }).then( | |||||
response => { | |||||
if (response.ok) { | |||||
this.errorText = `Success!` | |||||
this.completed = true | |||||
this.$emit('updateUser') | |||||
this.$emit('updateOrders') | |||||
} else if (response.status == 520) { | |||||
this.errorText = 'Insuficient Credits' | |||||
} else { | |||||
this.errorText = `Error ${response.status}: | |||||
${response.statusText}` | |||||
} | |||||
this.paying = false | |||||
} | |||||
) | |||||
} | |||||
function page() { | |||||
switch (this.active) { | |||||
case '#new-order': | |||||
return 'new-order' | |||||
case '#credits': | |||||
return 'credits' | |||||
} | |||||
} | |||||
export default { | |||||
data() { | |||||
return {servicePane: true, services: null, selected: null, amount: 0, | |||||
paying: false, url: '', completed: false, errorText: ''} | |||||
}, | |||||
components: {ServicePane, Loading, Credits}, | |||||
props: ['token', 'credits', 'active', 'preferred'], | |||||
created() { | |||||
fetch("/panel/services", { | |||||
method: 'GET', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token} | |||||
}).then(response => { | |||||
response.json().then(data => {this.services = data}) | |||||
}) | |||||
}, | |||||
methods: {select, buyService}, | |||||
computed: {cost, page}, | |||||
emits: ['updateUser', 'updateOrders'] | |||||
} | |||||
</script> |
@@ -0,0 +1,119 @@ | |||||
<template> | |||||
<div> | |||||
<h2>Settings</h2> | |||||
<section class="change-name-pane"> | |||||
<h4>Name</h4> | |||||
<input :value="user.name" name="name" id="changed_name" type="text"> | |||||
<button @click="changeName">Save <loading src="../../images/loading-white.svg" alt=""></loading></button> | |||||
<span></span> | |||||
</section> | |||||
<section class="change-email-pane"> | |||||
<h4>Email</h4> | |||||
<input :value="user.email" name="email" type="text" id="changed_email"> | |||||
<button @click="changeEmail">Save<img class="loading-icon" src="../../images/loading-white.svg" alt=""></button> | |||||
<span></span> | |||||
</section> | |||||
<section class="change-password-pane"> | |||||
<h4>Change Password</h4> | |||||
<h5>Current Password</h5><input name="current_passowrd" id="current_password" type="password"> | |||||
<h5>New Password</h5><input id="new_password" name="password" type="password"> | |||||
<h5>Confirm Password</h5><input id="confirm_password" name="confirm_passowrd" type="password"> | |||||
<button @click="changePassword">Save<img class="loading-icon" src="../../images/loading-white.svg" alt=""></button> | |||||
<span></span> | |||||
</section> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import Loading from '../icons/loading.vue' | |||||
function changeName() { | |||||
let name = document.getElementById('changed_name').value | |||||
let info = document.querySelector('.change-name-pane span') | |||||
let pane = document.querySelector('.change-name-pane') | |||||
pane.classList.add('loading') | |||||
pane.classList.remove('error') | |||||
fetch("/panel/change-name", { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({'name': name}), | |||||
}).then(response => { | |||||
if (response.ok) { | |||||
pane.classList.add('completed') | |||||
info.textContent = 'Completed' | |||||
} else { | |||||
pane.classList.add('error') | |||||
info.textContent = 'Error: ' + response.status | |||||
} | |||||
pane.classList.remove('loading') | |||||
}) | |||||
} | |||||
function changeEmail() { | |||||
let email = document.getElementById('changed_email').value | |||||
let info = document.querySelector('.change-email-pane span') | |||||
let pane = document.querySelector('.change-email-pane') | |||||
pane.classList.add('loading') | |||||
pane.classList.remove('error') | |||||
fetch("/panel/change-email", { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({'email': email}), | |||||
}).then(response => { | |||||
if (response.ok) { | |||||
pane.classList.add('completed') | |||||
info.textContent = 'Verification link sent' | |||||
} else { | |||||
pane.classList.add('error') | |||||
info.textContent = 'Error: ' + response.status | |||||
} | |||||
pane.classList.remove('loading') | |||||
}) | |||||
} | |||||
function changePassword() { | |||||
let info = document.querySelector('.change-password-pane span') | |||||
let pane = document.querySelector('.change-password-pane') | |||||
pane.classList.add('loading') | |||||
pane.classList.remove('error') | |||||
fetch("/panel/change-password", { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({'current_password': | |||||
document.getElementById('current_password').value, | |||||
'password': document.getElementById('new_password').value, | |||||
'password_confirmation': | |||||
document.getElementById('confirm_password').value}), | |||||
}).then(response => { | |||||
response.json().then(data => {console.log(data)}) | |||||
if (response.ok) { | |||||
pane.classList.add('completed') | |||||
info.textContent = 'Completed' | |||||
} else { | |||||
pane.classList.add('error') | |||||
info.textContent = 'Error: ' + response.status | |||||
} | |||||
pane.classList.remove('loading') | |||||
}) | |||||
} | |||||
export default { | |||||
components: {Loading,}, | |||||
methods: { | |||||
changePassword, changeName, changeEmail | |||||
}, | |||||
props: ['user', 'token'] | |||||
} | |||||
</script> |
@@ -0,0 +1,46 @@ | |||||
<template> | |||||
<nav id="sidebar"> | |||||
<a :class="{selected: active == ''}" href="/panel#"> | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-house-door-fill" viewBox="0 0 16 16"> | |||||
<path d="M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5z"/> | |||||
</svg> | |||||
</a> | |||||
<a :class="{selected: active == '#orders'}" href="/panel#orders"> | |||||
<svg fill="currentColor" enable-background="new 0 0 24 24" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m16.12 1.929-10.891 5.576-4.329-2.13 10.699-5.283c.24-.122.528-.122.78 0z"/><path d="m23.088 5.375-11.082 5.49-4.15-2.045-.6-.305 10.903-5.575.6.304z"/><path d="m11.118 12.447-.012 11.553-10.614-5.539c-.3-.158-.492-.475-.492-.816v-10.688l4.498 2.216v3.896c0 .499.408.913.9.913s.9-.414.9-.913v-2.995l.6.292z"/><path d="m23.988 6.969-11.07 5.466-.012 11.553 11.094-5.793z"/></svg> | |||||
</a> | |||||
<a :class="{selected: active == '#new-order' || active == '#credits'}" href="/panel#new-order"> | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M12,2C6.486,2,2,6.486,2,12s4.486,10,10,10c5.514,0,10-4.486,10-10S17.514,2,12,2z M17,13h-4v4h-2v-4H7v-2h4V7h2v4h4V13z"></path></svg> | |||||
</a> | |||||
<a :class="{selected: active == '#settings'}" href="/panel#settings"> | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear-fill" viewBox="0 0 16 16"> | |||||
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/> | |||||
</svg> | |||||
</a> | |||||
<a :class="{selected: active == '#support'}" href="/panel#support"> | |||||
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-life-preserver" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |||||
<path fill-rule="evenodd" d="M14.43 10.772l-2.788-1.115a4.015 4.015 0 0 1-1.985 1.985l1.115 2.788a7.025 7.025 0 0 0 3.658-3.658zM5.228 14.43l1.115-2.788a4.015 4.015 0 0 1-1.985-1.985L1.57 10.772a7.025 7.025 0 0 0 3.658 3.658zm9.202-9.202a7.025 7.025 0 0 0-3.658-3.658L9.657 4.358a4.015 4.015 0 0 1 1.985 1.985l2.788-1.115zm-8.087-.87L5.228 1.57A7.025 7.025 0 0 0 1.57 5.228l2.788 1.115a4.015 4.015 0 0 1 1.985-1.985zM8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm0-5a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/> | |||||
</svg> | |||||
</a> | |||||
<a v-if="role == 'admin'" :class="{selected: active == '#admin'}" href="/telescope"> | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-key-fill" viewBox="0 0 16 16"> | |||||
<path d="M3.5 11.5a3.5 3.5 0 1 1 3.163-5H14L15.5 8 14 9.5l-1-1-1 1-1-1-1 1-1-1-1 1H6.663a3.5 3.5 0 0 1-3.163 2zM2.5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/> | |||||
</svg> | |||||
</a> | |||||
<a :class="{selected: active == '#exit'}" href="/panel#exit"> | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-door-open-fill" viewBox="0 0 16 16"> | |||||
<path d="M1.5 15a.5.5 0 0 0 0 1h13a.5.5 0 0 0 0-1H13V2.5A1.5 1.5 0 0 0 11.5 1H11V.5a.5.5 0 0 0-.57-.495l-7 1A.5.5 0 0 0 3 1.5V15H1.5zM11 2h.5a.5.5 0 0 1 .5.5V15h-1V2zm-2.5 8c-.276 0-.5-.448-.5-1s.224-1 .5-1 .5.448.5 1-.224 1-.5 1z"/> | |||||
</svg> | |||||
</a> | |||||
</nav> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
props: ['active', 'role'] | |||||
} | |||||
</script> | |||||
@@ -0,0 +1,3 @@ | |||||
<template> | |||||
<div id="main">important info here</div> | |||||
</template> |
@@ -0,0 +1,82 @@ | |||||
<template> | |||||
<div class="support-section" id="main"> | |||||
<h2>Support</h2> | |||||
<loading v-if="loading"></loading> | |||||
<div v-if="!loading && complete" class="dialog"> | |||||
<img class="icon" src="../../images/checked2.svg" alt=""> | |||||
<h3>Ticket sent. An administrator will contact you soon.</h3> | |||||
</div> | |||||
<div v-if="!loading && !complete" id="support-form"> | |||||
<label for="">Topic</label> | |||||
<select id="support-topic" name="" v-model="topic"> | |||||
<option value="order">Order</option> | |||||
<option value="service">Service</option> | |||||
<option value="credits">Credits</option> | |||||
<option value="payment">Payment</option> | |||||
<option value="other">Other</option> | |||||
</select> | |||||
<label for="">Details</label> | |||||
<textarea id="" name="" cols="30" rows="10" v-model="message"></textarea> | |||||
<span class="note-grey">Include any relevant information like the order number, | |||||
service name, etc</span> | |||||
<button @click="send">Submit</button> | |||||
<p class="error-message">{{errorMessage}}</p> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import Loading from '../icons/loading.vue' | |||||
function send() { | |||||
this.errorMessage = '' | |||||
if (!this.topic || !this.message) { | |||||
this.errorMessage = 'Topic and details cannot be blank.' | |||||
return | |||||
} | |||||
this.loading = true | |||||
fetch("/panel/support", { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({'topic': this.topic, 'message': this.message})}). | |||||
then(response => { | |||||
if (response.ok) { | |||||
this.complete = true | |||||
} else { | |||||
this.complete = false | |||||
this.error = true | |||||
this.errorMessage = `${response.status}: | |||||
${response.statusText}` | |||||
} | |||||
this.loading = false | |||||
}) | |||||
} | |||||
export default { | |||||
components: {Loading}, | |||||
props: ['user', 'token'], | |||||
data() { | |||||
return {loading: false, complete: false, error: false, errorMessage: | |||||
'', topic: null, message: ''} | |||||
}, | |||||
methods: {send} | |||||
} | |||||
</script> |
@@ -0,0 +1,33 @@ | |||||
<template> | |||||
<div v-once id="main"> | |||||
<div v-if="active == '#transaction-complete' && user.paying" class="status-dialog"> | |||||
<img class="icon" src="../../images/checked2.svg" alt=""/> | |||||
<h3>Purchase complete.</h3> | |||||
</div> | |||||
<div v-if="active == '#transaction-failed' && user.paying" class="status-dialog"> | |||||
<img class="icon" src="../../images/warning-colored.svg" alt=""/> | |||||
<h3>Purchase failed.</h3> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
props: ['token', 'user', 'active'], | |||||
emits: ['purchaseComplete'], | |||||
//Should check that user is actualling in the paying state. If so, send get | |||||
//request to panel/transaction-end. It's then() should emit a payment | |||||
//complete to panel for a user state refresh. | |||||
mounted() { | |||||
fetch('/panel/clear-paying', { | |||||
method: 'GET', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}}).then(() => this.$emit('purchaseComplete')) | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,168 @@ | |||||
<template> | |||||
<form v-if="active === 'register'" onsubmit = "event.preventDefault(); return false" id="register-form"> | |||||
<h3>Registration</h3> | |||||
<div>{{errorMessage}}</div> | |||||
<div> | |||||
<label for='sender_name'>Name</label> | |||||
<input id='register-name' required type='name' name='sender_name' placeholder='' | |||||
spellcheck='false'> | |||||
</div> | |||||
<div> | |||||
<label for='sender_email'>Email</label> | |||||
<input v-on:input="checkPasswords" id='register-email' required type='email' name='sender_email' placeholder='' | |||||
spellcheck='false'> | |||||
</div> | |||||
<div> | |||||
<label for='sender_password'>Password</label> | |||||
<input v-on:input="checkPasswords" id='register-password' required type='password' name='sender_password' | |||||
placeholder='' spellcheck='false'> | |||||
</div> | |||||
<div> | |||||
<label for='confirm_password'>Confirm Password</label> | |||||
<input v-on:input="checkPasswords" id='confirm-password' required type='password' | |||||
name='sender_password' placeholder='' spellcheck='false'> | |||||
</div> | |||||
<button @click="register($event)" class="submit-btn" type="submit">Submit</button> | |||||
</form> | |||||
<form v-if="active === 'forgot'" v-on:submit="forgotPassword" id="forgot-form"> | |||||
<h3>Forgot Password</h3> | |||||
<div> | |||||
<label for='sender_email'>Email</label> | |||||
<input id='forgot-email' required type='email' name='sender_email' placeholder='' | |||||
spellcheck='false'> | |||||
</div> | |||||
<button class="submit-btn" type="submit">Submit</button> | |||||
</form> | |||||
<img v-if="active === 'loading'" type="image/svg+xml" class="loading-icon" src="../../images/loading.svg" alt=""/> | |||||
<div v-if="active === 'register-completed'"> | |||||
<img class="medium-icon" src="../../images/checked2.svg" alt=""> | |||||
<h3>Success!</h3> | |||||
<p>A verification link has been sent to your inbox.</p> | |||||
</div> | |||||
<div v-if="active === 'forgot-completed'"> | |||||
<img class="medium-icon" src="../../images/checked2.svg" alt=""> | |||||
<h3>Success!</h3> | |||||
<p>A password reset link has been sent.</p> | |||||
</div> | |||||
<div v-if="active === 'error'"> | |||||
<img class="medium-icon" src="../../images/warning-colored.svg" alt=""> | |||||
<h3>An Error Occured.</h3> | |||||
<p>{{`${error}: ${errorMessage}`}}</p> | |||||
</div> | |||||
<div v-on:click="closeArea" class="cancel-button"></div> | |||||
</template> | |||||
<script> | |||||
function getCookie(name) { | |||||
var re = new RegExp(name + "=([^;]+)") | |||||
var value = re.exec(document.cookie) | |||||
let v = (value != null) ? unescape(value[1]) : null | |||||
return v | |||||
} | |||||
function getToken() { | |||||
return fetch("/sanctum/csrf-cookie", { | |||||
method: 'GET' | |||||
}).then( () => { | |||||
this.token = this.getCookie('XSRF-TOKEN') | |||||
return this.token | |||||
}) | |||||
} | |||||
function register(event) { | |||||
event.preventDefault(); | |||||
event.stopPropagation(); | |||||
this.errorMessage = '' | |||||
let name = document.getElementById("register-name").value | |||||
let email = document.getElementById("register-email").value | |||||
let password = document.getElementById("register-password").value | |||||
let password_confirmation = document.getElementById("confirm-password").value | |||||
this.active = 'loading' | |||||
this.getToken().then(() => {fetch("/register", { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({"name": name, | |||||
"email": email, | |||||
"password": password, | |||||
"password_confirmation": password_confirmation})}) | |||||
.then(response => { | |||||
//Give completed or error | |||||
if (response.ok) { | |||||
this.active = 'register-completed' | |||||
} else if (response.status != 500) { | |||||
response.json().then((e) => { | |||||
try { | |||||
for (let i in e.errors) { | |||||
this.errorMessage = e.errors[i].flat().join("\n") + | |||||
"\n" + this.errorMessage | |||||
} | |||||
} catch (x) { | |||||
this.errorMessage = e.message | |||||
} | |||||
}) | |||||
this.active = 'register' | |||||
} else { | |||||
this.errorMessage = response.statusText | |||||
this.active = 'register' | |||||
} | |||||
}); | |||||
}) | |||||
return false | |||||
} | |||||
function checkPasswords() { | |||||
let passInput = document.getElementById('register-password') | |||||
let passInput2 = document.getElementById('confirm-password') | |||||
if (passInput.value != passInput2.value) { | |||||
passInput2.setCustomValidity('Passwords must be matching') | |||||
} else { | |||||
passInput2.setCustomValidity(''); | |||||
} | |||||
} | |||||
function forgotPassword(event) { | |||||
let email = document.getElementById("forgot-email").value | |||||
this.active = 'loading' | |||||
fetch("/forgot-password", { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
body: JSON.stringify({"email": email})}) | |||||
.then(response => { | |||||
if (response.ok) { | |||||
this.active = 'forgot-completed' | |||||
} else { | |||||
this.error = response.status | |||||
this.errorMessage = response.statusText | |||||
this.active = 'error' | |||||
} | |||||
/* console.log(response.json()) */ | |||||
}); | |||||
event.preventDefault(); | |||||
} | |||||
module.exports = { | |||||
data() { | |||||
return {active: 'register', token: '', errorMessage: ''} | |||||
}, | |||||
methods: { | |||||
getToken, | |||||
getCookie, | |||||
register, | |||||
checkPasswords, | |||||
forgotPassword, | |||||
closeArea() { | |||||
document.querySelector(".register-area").classList.remove("active") | |||||
}, | |||||
}, | |||||
mounted() { | |||||
this.getToken() | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,19 @@ | |||||
<?php | |||||
use Illuminate\Http\Request; | |||||
use Illuminate\Support\Facades\Route; | |||||
/* | |||||
|-------------------------------------------------------------------------- | |||||
| API Routes | |||||
|-------------------------------------------------------------------------- | |||||
| | |||||
| Here is where you can register API routes for your application. These | |||||
| routes are loaded by the RouteServiceProvider within a group which | |||||
| is assigned the "api" middleware group. Enjoy building your API! | |||||
| | |||||
*/ | |||||
Route::middleware('auth:api')->get('/user', function (Request $request) { | |||||
return $request->user(); | |||||
}); |
@@ -0,0 +1,18 @@ | |||||
<?php | |||||
use Illuminate\Support\Facades\Broadcast; | |||||
/* | |||||
|-------------------------------------------------------------------------- | |||||
| Broadcast Channels | |||||
|-------------------------------------------------------------------------- | |||||
| | |||||
| Here you may register all of the event broadcasting channels that your | |||||
| application supports. The given channel authorization callbacks are | |||||
| used to check if an authenticated user can listen to the channel. | |||||
| | |||||
*/ | |||||
Broadcast::channel('App.Models.User.{id}', function ($user, $id) { | |||||
return (int) $user->id === (int) $id; | |||||
}); |
@@ -0,0 +1,19 @@ | |||||
<?php | |||||
use Illuminate\Foundation\Inspiring; | |||||
use Illuminate\Support\Facades\Artisan; | |||||
/* | |||||
|-------------------------------------------------------------------------- | |||||
| Console Routes | |||||
|-------------------------------------------------------------------------- | |||||
| | |||||
| This file is where you may define all of your Closure based console | |||||
| commands. Each Closure is bound to a command instance allowing a | |||||
| simple approach to interacting with each command's IO methods. | |||||
| | |||||
*/ | |||||
Artisan::command('inspire', function () { | |||||
$this->comment(Inspiring::quote()); | |||||
})->purpose('Display an inspiring quote'); |
@@ -0,0 +1,162 @@ | |||||
<?php | |||||
use Illuminate\Support\Facades\Route; | |||||
use App\Http\Controllers\UserController; | |||||
use App\Http\Controllers\ServiceController; | |||||
use App\Http\Controllers\OrderController; | |||||
use App\Http\Controllers\BillingController; | |||||
use App\Http\Controllers\Ticket; | |||||
use Illuminate\Foundation\Auth\EmailVerificationRequest; | |||||
use Illuminate\Http\Request; | |||||
use Illuminate\Support\Facades\Auth; | |||||
/* | |||||
|-------------------------------------------------------------------------- | |||||
| Web Routes | |||||
|-------------------------------------------------------------------------- | |||||
| | |||||
| Here is where you can register web routes for your application. These | |||||
| routes are loaded by the RouteServiceProvider within a group which | |||||
| contains the "web" middleware group. Now create something great! | |||||
| | |||||
*/ | |||||
Route::get('/', function () { | |||||
if (Auth::check()) { | |||||
return redirect('panel'); | |||||
} | |||||
return view('home'); | |||||
})->name('login'); | |||||
Route::view('/panel', 'panel')->middleware([ 'auth', 'verified' ])->name('panel'); | |||||
Route::view('/terms-and-policy', 'terms-and-policy'); | |||||
//Verification routes | |||||
Route::get('/verify-email', function() { | |||||
if (Auth::user()->email_verified_at) { | |||||
abort(404); | |||||
} else { | |||||
return view('verify-email'); | |||||
} | |||||
})->middleware('auth')->name('verification.notice'); | |||||
Route::get('/verify/{id}/{hash}', function (EmailVerificationRequest $request) { | |||||
$request->fulfill(); | |||||
return redirect('/panel'); | |||||
})->middleware(['auth', 'signed'])->name('verification.verify'); | |||||
Route::post('/resend-verification', function (Request $request) { | |||||
if (Auth::user()->email_verified_at) { | |||||
abort(404); | |||||
} | |||||
$request->user()->sendEmailVerificationNotification(); | |||||
return back()->with('message', 'Verification link sent!'); | |||||
})->middleware(['auth', 'throttle:6,1'])->name('verification.send'); | |||||
//Creation and recovery | |||||
Route::post('/register', [UserController::class, 'create']); | |||||
Route::post('/forgot-password', [UserController::class, | |||||
'forgotPassword'])->middleware('guest')->name('password.email'); | |||||
Route::get('/reset-password/{token}', function ($token) { | |||||
return view('reset-password', ['token' => $token]); | |||||
})->middleware('guest')->name('password.reset'); | |||||
Route::post('/reset-passowrd', [UserController::class, | |||||
'resetPassword'])->middleware('guest'); | |||||
Route::post('/login', [UserController::class, | |||||
'login'])->middleware('guest'); | |||||
Route::get('/logout', [UserController::class, | |||||
'logout'])->middleware('auth'); | |||||
//These should probably be grouped by name later | |||||
Route::get('/panel/user', function (Request $request) { | |||||
return $request->user(); | |||||
})->middleware([ 'auth', 'verified' ]); | |||||
Route::post('/panel/change-card', function (Request $request) { | |||||
return App::make(BillingController::class)->changeDefaultCard($request->card); | |||||
})->middleware([ 'auth', 'verified' ]); | |||||
Route::post('/panel/delete-card', [BillingController::class, | |||||
'deleteCard'])->middleware([ 'auth', 'verified' ]); | |||||
Route::get('/panel/orders', [UserController::class, | |||||
'getOrders'])->middleware([ 'auth', 'verified' ]); | |||||
Route::get('/panel/services', [ServiceController::class, | |||||
'getServices'])->middleware([ 'auth', 'verified' ]); | |||||
Route::post('/panel/change-name', [UserController::class, | |||||
'changeName'])->middleware([ 'auth', 'verified' ]); | |||||
Route::post('/panel/change-email', [UserController::class, | |||||
'changeEmail'])->middleware([ 'auth', 'verified' ]); | |||||
Route::post('/panel/change-password', [UserController::class, | |||||
'changePassword'])->middleware([ 'auth', 'verified' ]); | |||||
Route::get('/reset-email', [UserController::class, | |||||
'resetEmail'])->name('reset-email'); | |||||
Route::post('/panel/orders', [OrderController::class, | |||||
'newOrder'])->middleware([ 'auth', 'verified' ]); | |||||
Route::post('/panel/secret', [BillingController::class, | |||||
'stripeSecret'])->middleware([ 'auth', 'verified' ]); | |||||
//Initiate a payeer payment | |||||
Route::post('/panel/payeer', [BillingController::class, | |||||
'payeer'])->middleware([ 'auth', 'verified' ]); | |||||
//Initiate a Perfect Money payment | |||||
Route::post('/panel/pm', [BillingController::class, | |||||
'pm'])->middleware([ 'auth', 'verified' ]); | |||||
Route::post('/panel/pm-complete', [BillingController::class, | |||||
'completePM'])->middleware([ 'auth', 'verified' ]); | |||||
Route::post('/panel/pm-fail', [BillingController::class, | |||||
'failPM'])->middleware([ 'auth', 'verified' ]); | |||||
Route::get('/panel/cards', [BillingController::class, | |||||
'getCards'])->middleware([ 'auth', 'verified' ]); | |||||
Route::post('/panel/save-url', [OrderController::class, | |||||
'changeURL'])->middleware([ 'auth', 'verified' ]); | |||||
//Stripe webhooks | |||||
Route::post('/hooks/charge', | |||||
[BillingController::class, 'chargeEvent']); | |||||
//Payeer handler function | |||||
Route::post('/hooks/payeer-transaction', | |||||
[BillingController::class, 'processPayeer']); | |||||
//PM handler function | |||||
Route::post('/hooks/pm-transaction', | |||||
[BillingController::class, 'processPM']); | |||||
//Payment attempt is over | |||||
Route::get('/panel/clear-paying', | |||||
[UserController::class, 'clearPaying'])->middleware(['auth', 'verified']); | |||||
Route::post('/panel/support', | |||||
[Ticket::class, 'send'])->middleware(['auth', 'verified']); | |||||
Route::get('/panel/transaction-failed', function (Request $request) { | |||||
return redirect('/panel#transaction-failed'); | |||||
})->middleware(['auth', 'verified']); | |||||
Route::get('/panel/transaction-complete', function (Request $request) { | |||||
return redirect('/panel#transaction-complete'); | |||||
})->middleware(['auth', 'verified']); |
@@ -0,0 +1,152 @@ | |||||
@use "sass:map"; | |||||
@use "sass:color"; | |||||
$theme-colors: ( | |||||
"red-alert": #ce0018, | |||||
"red-orangish": #de493b, | |||||
"blue": #0736a4, | |||||
"light-blue": #a4bfd9, | |||||
"medium-blue": #1877f2, | |||||
"dark-blue": #09c6f9, | |||||
"orange": #ec9f05, | |||||
"dark-orange": #ff4e00, | |||||
"brand-orange": #f67602, | |||||
"grey": #e6ebf1, | |||||
"light-grey": #e9e9e9, | |||||
"light2-grey": #bebebe, | |||||
"light3-grey": rgb(245,245,245), | |||||
"dark-grey": #212121, | |||||
"faded-text": #425466, | |||||
"faded-text2": #6c757d, | |||||
"dark-green": #3bb78f, | |||||
"light-green": #0bab64, | |||||
"green": #63b521, | |||||
"alt-blue-light": #e2f5ff, | |||||
"alt-blue-medium": #2b96cc, | |||||
"text-blue-medium": #425466, | |||||
"alt-red-light": #f3beb8, | |||||
"alt-red-medium": #f09898, | |||||
); | |||||
@function getColor($col) { | |||||
@return map.get($theme-colors, $col); | |||||
} | |||||
@function darkenColor($col) { | |||||
$oldCol : map.get($theme-colors, $col); | |||||
$newCol : color.scale($oldCol, $lightness: -45%); | |||||
@return $newCol; | |||||
} | |||||
@function lightenColor($col) { | |||||
$oldCol : map.get($theme-colors, $col); | |||||
$newCol : color.scale($oldCol, $lightness: 15%); | |||||
@return $newCol; | |||||
} | |||||
@mixin button($col) { | |||||
display: inline-block; | |||||
//font-family: $btn-font-family; | |||||
font-weight: 400; | |||||
background-color: getColor($col); | |||||
text-align: center; | |||||
vertical-align: middle; | |||||
user-select: none; | |||||
border: none; | |||||
padding: 7px 15px; | |||||
border-radius: 5px; | |||||
&:hover{ | |||||
background-color: darkenColor($col); | |||||
} | |||||
} | |||||
// A linear gradient with the light color at the top left and dark color at | |||||
// bottom right, that darkens on hover | |||||
@mixin special-button($start, $end) { | |||||
font-family: "PatuaOne"; | |||||
height: 2em; | |||||
border: none; | |||||
border-radius: 4px; | |||||
padding: 3px; | |||||
color: white; | |||||
background-color: #045de9; | |||||
background-image: linear-gradient(315deg, map.get($theme-colors, $start) 0%, map.get($theme-colors, $end) 74%); | |||||
&:hover { | |||||
background-image: linear-gradient(315deg, darkenColor($start) 0%, darkenColor($end) 74%); | |||||
} | |||||
} | |||||
// A white button with a transparent background that becomes white and orange on hover | |||||
@mixin transparent-button { | |||||
text-align: center; | |||||
font-size: 20px; | |||||
border: 2px solid white; | |||||
border-radius: 4px; | |||||
padding: 10px; | |||||
background-color: transparent; | |||||
color: white; | |||||
&:hover { | |||||
background-color: white; | |||||
color: map.get($theme-colors, "orange"); | |||||
} | |||||
} | |||||
@mixin inverting-button($primary, $background) { | |||||
text-align: center; | |||||
border: 2px solid $primary; | |||||
border-radius: 4px; | |||||
padding: 5px 10px; | |||||
background-color: $background; | |||||
color: $primary; | |||||
&:hover { | |||||
background-color: $primary; | |||||
color: $background; | |||||
} | |||||
} | |||||
// A button with a white background and specified accent color that reverses on hover | |||||
@mixin brand-button($col) { | |||||
width: 10em; | |||||
height: 2.5em;; | |||||
background-color: white; | |||||
border: 2px solid map.get($theme-colors, $col); | |||||
color: map.get($theme-colors, $col); | |||||
border-radius: 4px; | |||||
font-size: 16px; | |||||
&:hover { | |||||
background-color: map.get($theme-colors, $col); | |||||
color: white; | |||||
} | |||||
} | |||||
@mixin hovering { | |||||
padding: 18px 9px; | |||||
border-radius: 4px; | |||||
background: white; | |||||
box-shadow: 7px 10px 8px rgb(156 166 175 / 22%); | |||||
transition: transform 0.2s; | |||||
} | |||||
@mixin hovering2 { | |||||
padding: 18px 9px; | |||||
border-radius: 4px; | |||||
background: white; | |||||
box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; | |||||
transition: transform 0.2s; | |||||
} | |||||
@mixin hovering3 { | |||||
padding: 18px 9px; | |||||
border-radius: 4px; | |||||
background: white; | |||||
box-shadow: rgba(0, 0, 0, 0.16) 0px 10px 36px 0px, rgba(0, 0, 0, 0.06) 0px 0px 0px 1px; | |||||
transition: transform 0.2s; | |||||
} | |||||
@mixin hovering-light { | |||||
padding: 18px 9px; | |||||
border-radius: 4px; | |||||
background: white; | |||||
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px; | |||||
transition: transform 0.2s; | |||||
} |
@@ -0,0 +1 @@ | |||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Vue App</title><link href="/css/app.a9fd8694.css" rel="preload" as="style"><link href="/js/app.0225a575.js" rel="preload" as="script"><link href="/js/chunk-vendors.b6c5b7d4.js" rel="preload" as="script"><link href="/css/app.a9fd8694.css" rel="stylesheet"></head><body><div id="app"></div><script src="/js/chunk-vendors.b6c5b7d4.js"></script><script src="/js/app.0225a575.js"></script></body></html> |
@@ -0,0 +1,12 @@ | |||||
<main class="body" id="ticket-email"> | |||||
<h3>An email change has been requested from {{$current_email}}</h3> | |||||
<div> | |||||
<a href="{{$link}}"><h4>Confirm</h4></a> | |||||
</div> | |||||
<div> | |||||
<small>If you do not recognize this activity, please disregard this | |||||
message.</small> | |||||
</div> | |||||
</main> |
@@ -0,0 +1,25 @@ | |||||
@extends('master') | |||||
@section('title', 'Settings') | |||||
@section('heading-style', 'info-page') | |||||
@section('head-metas') | |||||
@parent | |||||
<link rel="stylesheet" href="{{asset('panel.css')}}"> | |||||
@endsection | |||||
@section('content') | |||||
<main> | |||||
<div class="info-heading"> | |||||
<h3>Your email has been reset.</h3> | |||||
<a href="/panel"><button class="brand-button">Return</button></a> | |||||
</div> | |||||
</main> | |||||
@endsection | |||||
@section('scripts') | |||||
@parent | |||||
<script src="js/app.js"></script> | |||||
<script src="js/chunk-vendors.js"></script> | |||||
<!-- <script src="main.js"></script> --> | |||||
@endsection |
@@ -0,0 +1,228 @@ | |||||
@extends('master') | |||||
@section('title', 'Home') | |||||
@section('heading-style', 'Home') | |||||
@section('head-metas') | |||||
@parent | |||||
@endsection | |||||
@section('login-form') | |||||
@guest | |||||
<form id='login_form' class= 'login' method='POST'> | |||||
<div> | |||||
<label for='sender_email'>Email</label> | |||||
<input required id='login_email' type='email' name='sender_email' placeholder='' | |||||
spellcheck='false'> | |||||
</div> | |||||
<div> | |||||
<label for='sender_password'>Password</label> | |||||
<input required id='login_password' type='password' | |||||
name='sender_password' placeholder='' spellcheck='false'> | |||||
</div> | |||||
<button class='login-btn'type="login">Log in</button> | |||||
<div><p class="error"></p></div> | |||||
</form> | |||||
<div id="nav_toggle" class="nav-toggle"><div> | |||||
@endguest | |||||
@endsection | |||||
@section('content') | |||||
@parent | |||||
<main> | |||||
<div id="app" class="register-area"></div> | |||||
<div class='landing-hero'> | |||||
<div class='hero-filter'> | |||||
<h2>The web's #1 content promoter</h2> | |||||
<p>Give your brand the boost it needs for oganic growth with the | |||||
web's most trusted social media marketing panel.</p> | |||||
<a class='register-btn'>Register Now</a> | |||||
</div> | |||||
</div> | |||||
<div class='blue-background'><section class="about-us"> | |||||
<p> | |||||
We're a social media content promoter based in Canada, helping | |||||
entertainers and brands perfect their marketing campaigns through | |||||
targeted views and engagements. By providing impressions and | |||||
retention, we'll help boost your | |||||
rankings and get you noticed. | |||||
</p> | |||||
</section> | |||||
<section class='services-cards'> | |||||
<h2>Our Services</h2> | |||||
<div class="cards"> | |||||
<div class="card"> | |||||
<img src="/img/youtube-icon.svg" alt="youtube-icon"> | |||||
<h3>Youtube</h3> | |||||
<ul> | |||||
<li>Views increase within 24 hours</li> | |||||
<li>Boost to rankings</li> | |||||
<li>High quality accounts</li> | |||||
<li>Likes and Comments</li> | |||||
</ul> | |||||
<button>Select</button> | |||||
</div> | |||||
<div class="card"> | |||||
<img src="/img/twitter.svg" alt="spotify-icon"> | |||||
<h3>Twitter</h3> | |||||
<ul> | |||||
<li>Impressions and favorites</li> | |||||
<li>Activity increases within 24 hours</li> | |||||
<li>Followers</li> | |||||
</ul> | |||||
<button>Select</button> | |||||
</div> | |||||
<div class="card"> | |||||
<img src="/img/instagram-icon.svg" alt="twitter-icon"> | |||||
<h3>Instagram</h3> | |||||
<ul> | |||||
<li>Active user accounts</li> | |||||
<li>Activity increases within 24 hours</li> | |||||
<li>Likes</li> | |||||
<li>Followers</li> | |||||
</ul> | |||||
<button>Select</button> | |||||
</div> | |||||
<div class="card"> | |||||
<img src="/img/tik-tok.svg" alt="twitter-icon"> | |||||
<h3>TikTok</h3> | |||||
<ul> | |||||
<li>Active user accounts</li> | |||||
<li>Views</li> | |||||
<li>Likes</li> | |||||
<li>Followers</li> | |||||
</ul> | |||||
<button>Select</button> | |||||
</div> | |||||
</div></div> | |||||
</section> | |||||
<section class='features-info'> | |||||
<h2>Why We Are the Best</h2> | |||||
<div class="circle-prop"> | |||||
<svg height="100%" preserveAspectRatio="none" viewBox="0 0 500 500"fill='white' version="1.1" id="Layer_1" x="0px" y="0px" | |||||
xml:space="preserve"> | |||||
<path stroke-width="20" fill="none" stroke="#ddd" d="M0,10 C400,0,500,256,250,500"/> | |||||
<!- <path fill="none" stroke="white" d="M256,10C115.03,0,0,115.05,0,256c0,140.97,115.05,256,256,256c140.97,0,256-115.05,256-256C512,115.03,396.95,0,256,0z "/> --> | |||||
</svg> | |||||
<img src="img/teamwork2.png" alt="teamwork"> | |||||
</div> | |||||
<div class="info-cards"> | |||||
<div class="info-card"><p> | |||||
We help you save time managing your onlince presence while still providing | |||||
a high quality, and reliable service. | |||||
</p> | |||||
</div> | |||||
<div class="info-card"><p>All orders are proccessed within one day of | |||||
placing your order. Many services available are instant.</p> | |||||
</div> | |||||
<div class="info-card"><p>By combining our services to create trending | |||||
content, you'll improve the quality of viewer engagements and boost your | |||||
rankings significantly. We're the best marketing panel around.</p> </div> | |||||
<div class="info-card"><p> | |||||
Improve your brand's image, increase your social proof, and build a stronger | |||||
relationship with your audience.</p> | |||||
</div></div> | |||||
</section> | |||||
<section class='panel-infos'> | |||||
<h2>A User-Friendly Control Panel</h2> | |||||
<img class="panel-preview" src="/images/panel-preview.png" alt="panel-preview"> | |||||
<div class="info-items"> | |||||
<div class="panel-info"> | |||||
<img src="./img/tap.svg" alt=""> | |||||
<h3>Easy to Use</h3> | |||||
<p>We provide an extremely user-friendly and intuitive SPA interface to | |||||
manage and review your SMM orders.</p> | |||||
</div> | |||||
<div class="panel-info"> | |||||
<img src="./img/lighting.svg" alt=""> | |||||
<h3>Powerful</h3> | |||||
<p>Our panel contains a variety of features for managing payment | |||||
methods, and automatically queuing orders.</p> | |||||
</div> | |||||
<div class="panel-info"> | |||||
<img src="./img/statistics.svg" alt=""> | |||||
<h3>Scalable</h3> | |||||
<p>We make it easy to organize your marketing campaign with a variety | |||||
of accounts and social networks</p> | |||||
</div> | |||||
<div> | |||||
</section> | |||||
<section class='benefits-info'> | |||||
<h2>Benefits of Buying Views</h2> | |||||
<div> | |||||
<p>We know that getting people to see your content can be an uphill | |||||
battle. Reaching just 10,000 organic views can be tough because social networks give | |||||
the lion's share of exposure to the personalities who already have the biggest | |||||
following. The more views and impressions a post has, the more people are | |||||
willing to watch it, that's why consistent and high engagement is | |||||
essential to your brand's online growth. </p> | |||||
<p>Give your content the fair chance it deserves with a boost to your | |||||
account's traffic. Our SMM panel will help you build your audience, | |||||
develop your brand, and launch your new business.</p> | |||||
</div> | |||||
</section> | |||||
<section class='faq-info'> | |||||
<h2>FAQ</h2> | |||||
<div class="collapsibles"> | |||||
<div class="collapsible"><button>Why should I choose Trendplays?</button> | |||||
<div class="content"><p>Buying views and engagements is easy and safe | |||||
if you buy from a reliable source. The traffic we send to your content is | |||||
from accounts with years of history, viewing | |||||
gradually over a few days.</p></div></div> | |||||
<div class="collapsible"><button>What if my audience finds out?</button> | |||||
<div class="content"><p> At Trendplays, we | |||||
pride ourselves on our capacity for discretion and professionalism. | |||||
</p></div></div> | |||||
<div class="collapsible"><button>Will my account be safe?</button> | |||||
<div class="content"><p> | |||||
While there is some risk of disciplinary action, | |||||
it is rare and mild because, if buying views were made a breach in | |||||
the TOS that resulted in a ban, then anybody could send those views to | |||||
their competitor's account. | |||||
</p></div></div> | |||||
<div class="collapsible"><button>Is buying views illegal?</button> | |||||
<div class="content"><p> | |||||
No, and there is no good reason it should be. | |||||
</p></div></div> | |||||
<div class="collapsible"><button>How can I pay?</button> | |||||
<div class="content"><p> | |||||
We accept Debit/Credit or your choice of crytocurrency | |||||
through our Payeer and Perfect Money payment providers. | |||||
</p></div></div> | |||||
</div> | |||||
</section> | |||||
</main> | |||||
@endsection | |||||
@section('footer') | |||||
<footer class='footer text-center'> | |||||
<div class='foot-links'> | |||||
<a href="/terms-and-policy">Terms & Policy</a> | |||||
<a href="mail:support@trendplays.com">Help</a> | |||||
<a id="forgot-password-btn" href="#">Reset Password</a> | |||||
</div> | |||||
<small>©Copyright 2021 Trendplays Network, Inc.</small> | |||||
</footer> | |||||
@endsection | |||||
@section('scripts') | |||||
@parent | |||||
<script src="js/app.js"></script> | |||||
<script src="js/chunk-vendors.js"></script> | |||||
<!-- <script src="main.js"></script> --> | |||||
@endsection |
@@ -0,0 +1,61 @@ | |||||
<!DOCTYPE html> | |||||
<head> | |||||
{{-- This section may need to be overwritten in descendants --}} | |||||
@section('head-metas') | |||||
<meta charset='utf-8'> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | |||||
<link rel="stylesheet" href=""> | |||||
<link rel="shortcut icon" type="image/jpg" href="/img/arrow-up.svg"/> | |||||
<link rel="stylesheet" href="/css/app.css"> | |||||
@if (config('app.env') == 'production') | |||||
<!-- Global site tag (gtag.js) - Google Analytics --> | |||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LH36E9P3P5"></script> | |||||
<script> | |||||
window.dataLayer = window.dataLayer || []; | |||||
function gtag(){dataLayer.push(arguments);} | |||||
gtag('js', new Date()); | |||||
gtag('config', 'G-LH36E9P3P5'); | |||||
</script> | |||||
<!-- Global site tag (gtag.js) - Google Analytics --> | |||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-200591850-1"> | |||||
</script> | |||||
<script> | |||||
window.dataLayer = window.dataLayer || []; | |||||
function gtag(){dataLayer.push(arguments);} | |||||
gtag('js', new Date()); | |||||
gtag('config', 'UA-200591850-1'); | |||||
</script> | |||||
@endif | |||||
@show | |||||
<title>Trendplays - @yield('title')</title> | |||||
</head> | |||||
<body> | |||||
<nav class="navigator @yield('heading-style')" role="navigation"> | |||||
<a id='logo' href='/'> | |||||
<h1>Trendplays</h1> | |||||
<img src="/img/arrow-up.svg" height="30px" alt=""> | |||||
</a> | |||||
@yield('login-form') | |||||
<span class="icon-bar"></span> | |||||
</nav> | |||||
@yield('content') | |||||
@section('footer') | |||||
<footer class='footer text-center'> | |||||
<div class='foot-links'> | |||||
<a href="/terms-and-policy">Terms & Policy</a> | |||||
<a href="mail:support@trendplays.com">Help</a> | |||||
@auth <a href="/logout">Logout</a> @endauth | |||||
</div> | |||||
<small>©Copyright 2021 Trendplays Network, Inc.</small> | |||||
</footer> | |||||
@show | |||||
@section('scripts') | |||||
@show | |||||
</body> |
@@ -0,0 +1,25 @@ | |||||
@extends('master') | |||||
@section('title', 'Panel') | |||||
@section('child-type', 'panel') | |||||
@section('head-metas') | |||||
@parent | |||||
@endsection | |||||
@section('content') | |||||
@parent | |||||
<main class="panel"> | |||||
<div id="panel"> | |||||
</div> | |||||
</main> | |||||
@endsection | |||||
@section('footer') | |||||
@endsection | |||||
@section('scripts') | |||||
@parent | |||||
<script src="js/app.js"></script> | |||||
<script src="js/chunk-vendors.js"></script> | |||||
@endsection |
@@ -0,0 +1,17 @@ | |||||
@EXTEnds('master') | |||||
@section('title', 'Password Reset') | |||||
@section('head-metas') | |||||
@parent | |||||
<link rel="stylesheet" href="{{asset('main.css')}}"> | |||||
@endsection | |||||
@section('content') | |||||
Reset form goes here. | |||||
@endsection | |||||
@section('scripts') | |||||
@parent | |||||
<script src="main.js"></script> | |||||
@endsection |
@@ -0,0 +1,17 @@ | |||||
@extends('master') | |||||
@section('title', 'Password Reset') | |||||
@section('head-metas') | |||||
@parent | |||||
<link rel="stylesheet" href="{{asset('main.css')}}"> | |||||
@endsection | |||||
@section('content') | |||||
Reset form goes here. | |||||
@endsection | |||||
@section('scripts') | |||||
@parent | |||||
<script src="main.js"></script> | |||||
@endsection |
@@ -0,0 +1,24 @@ | |||||
@extends('master') | |||||
@section('title', 'Ticket') | |||||
@section('head-metas') | |||||
@parent | |||||
@endsection | |||||
@section('content') | |||||
@parent | |||||
<main id="ticket-email"> | |||||
<h3>Name: {{$name}}</h3> | |||||
<h3>Email: {{$email}}</h3> | |||||
<h3>Message:</h3> | |||||
<p> {{$user_message}} </p> | |||||
</main> | |||||
@endsection | |||||
@section('footer') | |||||
@endsection |
@@ -0,0 +1,221 @@ | |||||
@extends('master') | |||||
@section('title', 'Terms & Policy') | |||||
@section('heading-style', 'info-page') | |||||
@section('head-metas') | |||||
@parent | |||||
<link rel="stylesheet" href="{{asset('panel.css')}}"> | |||||
@endsection | |||||
@section('content') | |||||
@parent | |||||
<main class="terms"> | |||||
<section> | |||||
<h2>Terms and Conditions</h2> | |||||
<h4>SITE USAGE</h4> | |||||
<p>These terms and conditions outline the rules and regulations for the | |||||
use of this Website. By accessing this website we assume you accept these terms | |||||
and conditions in full. The following terminology applies to these Terms and | |||||
Conditions, Privacy Statement and Disclaimer Notice and any or all Agreements: | |||||
"Client", "You" and "Your" refers to you, the person accessing this website and | |||||
accepting the Company's terms and conditions. "The Company", "Ourselves", "We", | |||||
"Our" and "Us", refers to our Company. "Party", "Parties", or "Us", refers to | |||||
both the client and ourselves, or either the client or ourselves. </p> | |||||
<p> All terms refer to the offer, acceptance and consideration of payment necessary to | |||||
undertake the process of our assistance to the client in the most appropriate | |||||
manner, whether by formal meetings of a fixed duration, or any other means, for | |||||
the express purpose of meeting the client's needs in respect of provision of | |||||
the Company's stated services/products. Any use of the above terminology or | |||||
other words in the singular, plural, capitalisation and/or he/she or they, are | |||||
taken as interchangeable. | |||||
</p> | |||||
<p>Trendplays Network Inc. and any third-party providers make no warranty of any kind | |||||
regarding this site and/or any data or content provided on this site, all of | |||||
which are provided on an “as is” “as available” basis. We and any third party | |||||
providers do not warrant the accuracy, completeness, currency or reliability of | |||||
any of the content or data found on this site and such parties expressly | |||||
disclaim, to the fullest extent provided by law, all warranties and conditions, | |||||
including implied warranties and conditions of merchantability, fitness for a | |||||
particular purpose and non-infringement, and those arising by statute or | |||||
otherwise in law or from course of dealing or usage of trade. Neither We nor | |||||
any third party providers warrant that this site, its servers or any email sent | |||||
from us are free of viruses or other harmful components. | |||||
</p> | |||||
<h4>SITE CHANGES</h4> | |||||
<p> Trendplays Network Inc. reserves the right to change, edit, or delete | |||||
any documents, information, or other content appearing on the site or these | |||||
Terms and Conditions from time to time without notice and in its sole | |||||
discretion. All changes are effective immediately when posted and apply to all | |||||
access to, and use of, this site thereafter. However, any changes to the | |||||
governing law and dispute resolution provisions set forth below will not apply | |||||
to any disputes for which parties have actual notice on or before the date the | |||||
change is posted on the site. | |||||
</p> | |||||
<h4>COOKIES</h4> | |||||
<p>We employ the use of cookies. By using this website you consent to | |||||
the use of cookies in accordance with our privacy policy. Cookies are used in | |||||
some areas of this site to enable special functionality in those areas and | |||||
improve ease of use for our visitors.</p> | |||||
<h4>INDEMNIFICATION</h4> | |||||
<p> | |||||
You agree to indemnify, defend and hold harmless Trendplays Network Inc. and its | |||||
directors, officers, associates, agents, successors and assigns from and | |||||
against any claims, liabilities, damages, judgments, awards, losses, costs, | |||||
expenses and fees (including reasonable attorney fees) arising out of or | |||||
relating to your violation of these Terms and Conditions or your use of this | |||||
site. | |||||
</p> | |||||
<h4>NO JOINT VENTURE OR DEROGATION OF RIGHTS</h4> | |||||
<p> | |||||
You agree that no joint venture, partnership, employment, or agency | |||||
relationship exists between you and Trendplays Network Inc. as a result of these | |||||
Terms and Conditions or your use of the Site. Our performance of these | |||||
Terms and Conditions is subject to existing laws and legal process, and nothing | |||||
contained herein is in derogation of our right to comply with governmental, | |||||
court and law enforcement requests or requirements relating to your use of the | |||||
Site or information provided to or gathered by us with respect to such | |||||
use. | |||||
</p> | |||||
<h4>VISITOR COMMUNICATION</h4> | |||||
<p> | |||||
Except where expressly provided otherwise by Trendplays Network Inc., all comments, feedback, | |||||
information, or materials that you submit through or in association with the | |||||
Site shall be treated by you as confidential. By submitting such comments, | |||||
feedback, information, or materials to Trendplays Network Inc.: | |||||
</p> | |||||
<p> | |||||
i. You represent and warrant that Trendplays Network Inc.’s use of your | |||||
submission does not and will not breach any agreement, violate any law, or | |||||
infringe any third party’s | |||||
rights; </p> | |||||
<p> ii. You represent and warrant that you have all rights to enter into | |||||
these Terms and Conditions;</p> | |||||
<p> iii. Trendplays Network Inc. is free to use in any manner all or part of | |||||
the content of any such communications on an unrestricted basis without the | |||||
obligation to notify, identify or compensate you or anyone else; and</p> | |||||
<p>iv. You grant Trendplays Network Inc. all necessary rights, including a | |||||
waiver of all privacy and moral rights, to use all comments, feedback, | |||||
information, or materials, in whole or in part, or as a derivative work, | |||||
without any duty by Trendplays Network Inc. to anyone whatsoever.</p> | |||||
<p> Trendplays Network Inc. does not accept unsolicited ideas, works, or other | |||||
materials, and you acknowledge that you are responsible for and bear all risk | |||||
as to the use or distribution of any such ideas, works, or materials.</p> | |||||
<h4>SERVICE</h4> | |||||
<p>The rates shown on this site are subject to change at any time without notice.</p> | |||||
<p>Trendplays Network Ink, does not guarantee a delivery time for any | |||||
order, and acknowledge that some orders may not be feasible to complete. | |||||
The information provided is only the best estimation of when the order will be | |||||
completed.</p> | |||||
<p>Trendplays Network Inc., reserves the right to make modifications to a | |||||
service type if deemed necessary to complete an order.</p> | |||||
<p>Trendplays Network Inc., reserves the right to refund orders or | |||||
transactions at it's discretion.</p> | |||||
<p>Trendplays Network Inc, reserves the right to ban or delete an account | |||||
at any time without notice for any perceived breach of our terms.</p> | |||||
<p>All deposits are final. No refund will be made once credits have been | |||||
deposited to any account. All disputes or charge-backs will result in a ban | |||||
and termination of all future orders.</p> | |||||
<p>Fraudulent activity such as the use of a stolen credit card will result | |||||
in a ban without exceptions.</p> | |||||
<p> You may not run other marketing campaigns while your Trendplays Network | |||||
Inc., marketing campaign is running. Trendplays Network Inc., uses public | |||||
statistics to measure the results of it's campaigns, which other campaigns may | |||||
interfere with. If you do run other marketing campaigns simultaneously with a | |||||
Trendplays Network Inc., marketing campaign, then you agree that the Company is | |||||
responsible for every fan, follower, view, comment, like, visit, and/or vote | |||||
that you gain during the duration of the campaign.</p> | |||||
<p>The company shall in no way be liable for any accounts, photos, videos, | |||||
and/or tracks and that are removed due to a service being implemented or | |||||
your interaction with Trendplays Network Inc., It is nearly impossible to | |||||
determine why social media account providers suspend or delete accounts or | |||||
content. All purchases of Services are made at your own risk. For this reason, | |||||
Trendplays Network Inc., cannot refund credits after an order has completed. | |||||
</p> | |||||
</section> | |||||
</section> | |||||
<section> | |||||
<h2>Privacy Policy</h2> | |||||
<h4>YOUR PRIVACY MATTERS</h4> | |||||
<p> It is our policy to respect your privacy regarding any information we | |||||
may collect while operating our website. This Privacy Policy applies to any | |||||
of our products (hereinafter, "us", "we", or "any Trendplays Network Inc. products"). We | |||||
respect your privacy and are committed to protecting personally identifiable | |||||
information you may provide us through the Website. We have adopted this | |||||
privacy policy to explain what information may be collected | |||||
on our Website, how we use this information, and under what circumstances we | |||||
may disclose the information to third parties. This Privacy Policy applies only | |||||
to information we collect through the Website and does not apply to our | |||||
collection of information from other sources.</p> | |||||
<p>This Privacy Policy, together with the Terms and Conditions posted on | |||||
our Website, set forth the general rules and policies governing your use of our | |||||
Website. Depending on your activities when visiting our Website, you may be | |||||
required to agree to additional terms and conditions.</p> | |||||
<h4>VISITORS</h4> | |||||
<p>We collect non-personally-identifying information that web browsers and | |||||
servers typically make available, such as the browser type, language | |||||
preference, referring site, and the date and time of each visitor request. The | |||||
purpose of collecting this non-personally identifying information is to better | |||||
understand how our visitors use this website. From time to time, we may release | |||||
aggregate user statistics.</p> | |||||
<h4>IDENTIFYING INFORMATION</h4> | |||||
<p> Certain visitors to this site choose to interact with it in ways | |||||
that require us to gather personally-identifying information. The amount and | |||||
type of information that Trendplays Network Inc. gathers depends on the nature of | |||||
the interaction. For example, we ask visitors who sign up to provide an email | |||||
address.</p> | |||||
<h4>PROTECTION OF PERSONAL INFORMATION</h4> | |||||
<p>Trendplays Network Inc. discloses potentially personally-identifying | |||||
and personally-identifying information only to those of its employees, | |||||
contractors and affiliated organizations that (i) need to know that information | |||||
in order to process it on our behalf or to provide services available on this | |||||
website, and (ii) that have agreed not to disclose it to others. Some of those | |||||
employees, contractors and affiliated organizations may be located outside of | |||||
your home country; by using this website, you consent to the transfer of such | |||||
information to them.</p> | |||||
<p>Trendplays Network Inc. will not rent or sell potentially | |||||
personally-identifying and personally-identifying information to anyone. Other | |||||
than to its employees, contractors and affiliated organizations, as described | |||||
above, we disclose potentially personally-identifying and | |||||
personally-identifying information only in response to a subpoena, court order | |||||
or other governmental request, or when we believe in good faith that disclosure | |||||
is reasonably necessary to protect our property or rights. We take all | |||||
measures reasonably necessary to protect against the unauthorized access, use, | |||||
alteration or destruction of potentially personally-identifying information.</p> | |||||
</section> | |||||
</main> | |||||
@endsection | |||||
@section('scripts') | |||||
@parent | |||||
<script src="js/app.js"></script> | |||||
<script src="js/chunk-vendors.js"></script> | |||||
<!-- <script src="main.js"></script> --> | |||||
@endsection |
@@ -0,0 +1,19 @@ | |||||
<table class="action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation"> | |||||
<tr> | |||||
<td align="center"> | |||||
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"> | |||||
<tr> | |||||
<td align="center"> | |||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"> | |||||
<tr> | |||||
<td> | |||||
<a href="{{ $url }}" class="button button-{{ $color ?? 'primary' }}" target="_blank" rel="noopener">{{ $slot }}</a> | |||||
</td> | |||||
</tr> | |||||
</table> | |||||
</td> | |||||
</tr> | |||||
</table> | |||||
</td> | |||||
</tr> | |||||
</table> |
@@ -0,0 +1,11 @@ | |||||
<tr> | |||||
<td> | |||||
<table class="footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation"> | |||||
<tr> | |||||
<td class="content-cell" align="center"> | |||||
{{ Illuminate\Mail\Markdown::parse($slot) }} | |||||
</td> | |||||
</tr> | |||||
</table> | |||||
</td> | |||||
</tr> |
@@ -0,0 +1,11 @@ | |||||
<tr> | |||||
<td class="header"> | |||||
<a href="{{ $url }}" style="display: inline-block;"> | |||||
@if (trim($slot) === 'Laravel') | |||||
<img src="https://laravel.com/img/notification-logo.png" class="logo" alt="Laravel Logo"> | |||||
@else | |||||
{{ $slot }} | |||||
@endif | |||||
</a> | |||||
</td> | |||||
</tr> |
@@ -0,0 +1,56 @@ | |||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |||||
<html xmlns="http://www.w3.org/1999/xhtml"> | |||||
<head> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | |||||
<meta name="color-scheme" content="light"> | |||||
<meta name="supported-color-schemes" content="light"> | |||||
<style> | |||||
@media only screen and (max-width: 600px) { | |||||
.inner-body { | |||||
width: 100% !important; | |||||
} | |||||
.footer { | |||||
width: 100% !important; | |||||
} | |||||
} | |||||
@media only screen and (max-width: 500px) { | |||||
.button { | |||||
width: 100% !important; | |||||
} | |||||
} | |||||
</style> | |||||
</head> | |||||
<body> | |||||
<table class="wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation"> | |||||
<tr> | |||||
<td align="center"> | |||||
<table class="content" width="100%" cellpadding="0" cellspacing="0" role="presentation"> | |||||
{{ $header ?? '' }} | |||||
<!-- Email Body --> | |||||
<tr> | |||||
<td class="body" width="100%" cellpadding="0" cellspacing="0"> | |||||
<table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation"> | |||||
<!-- Body content --> | |||||
<tr> | |||||
<td class="content-cell"> | |||||
{{ Illuminate\Mail\Markdown::parse($slot) }} | |||||
{{ $subcopy ?? '' }} | |||||
</td> | |||||
</tr> | |||||
</table> | |||||
</td> | |||||
</tr> | |||||
{{ $footer ?? '' }} | |||||
</table> | |||||
</td> | |||||
</tr> | |||||
</table> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,27 @@ | |||||
@component('mail::layout') | |||||
{{-- Header --}} | |||||
@slot('header') | |||||
@component('mail::header', ['url' => config('app.url')]) | |||||
{{ config('app.name') }} | |||||
@endcomponent | |||||
@endslot | |||||
{{-- Body --}} | |||||
{{ $slot }} | |||||
{{-- Subcopy --}} | |||||
@isset($subcopy) | |||||
@slot('subcopy') | |||||
@component('mail::subcopy') | |||||
{{ $subcopy }} | |||||
@endcomponent | |||||
@endslot | |||||
@endisset | |||||
{{-- Footer --}} | |||||
@slot('footer') | |||||
@component('mail::footer') | |||||
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') | |||||
@endcomponent | |||||
@endslot | |||||
@endcomponent |
@@ -0,0 +1,14 @@ | |||||
<table class="panel" width="100%" cellpadding="0" cellspacing="0" role="presentation"> | |||||
<tr> | |||||
<td class="panel-content"> | |||||
<table width="100%" cellpadding="0" cellspacing="0" role="presentation"> | |||||
<tr> | |||||
<td class="panel-item"> | |||||
{{ Illuminate\Mail\Markdown::parse($slot) }} | |||||
</td> | |||||
</tr> | |||||
</table> | |||||
</td> | |||||
</tr> | |||||
</table> | |||||
@@ -0,0 +1,7 @@ | |||||
<table class="subcopy" width="100%" cellpadding="0" cellspacing="0" role="presentation"> | |||||
<tr> | |||||
<td> | |||||
{{ Illuminate\Mail\Markdown::parse($slot) }} | |||||
</td> | |||||
</tr> | |||||
</table> |
@@ -0,0 +1,3 @@ | |||||
<div class="table"> | |||||
{{ Illuminate\Mail\Markdown::parse($slot) }} | |||||
</div> |
@@ -0,0 +1,290 @@ | |||||
/* Base */ | |||||
body, | |||||
body *:not(html):not(style):not(br):not(tr):not(code) { | |||||
box-sizing: border-box; | |||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, | |||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; | |||||
position: relative; | |||||
} | |||||
body { | |||||
-webkit-text-size-adjust: none; | |||||
background-color: #ffffff; | |||||
color: #718096; | |||||
height: 100%; | |||||
line-height: 1.4; | |||||
margin: 0; | |||||
padding: 0; | |||||
width: 100% !important; | |||||
} | |||||
p, | |||||
ul, | |||||
ol, | |||||
blockquote { | |||||
line-height: 1.4; | |||||
text-align: left; | |||||
} | |||||
a { | |||||
color: #3869d4; | |||||
} | |||||
a img { | |||||
border: none; | |||||
} | |||||
/* Typography */ | |||||
h1 { | |||||
color: #3d4852; | |||||
font-size: 18px; | |||||
font-weight: bold; | |||||
margin-top: 0; | |||||
text-align: left; | |||||
} | |||||
h2 { | |||||
font-size: 16px; | |||||
font-weight: bold; | |||||
margin-top: 0; | |||||
text-align: left; | |||||
} | |||||
h3 { | |||||
font-size: 14px; | |||||
font-weight: bold; | |||||
margin-top: 0; | |||||
text-align: left; | |||||
} | |||||
p { | |||||
font-size: 16px; | |||||
line-height: 1.5em; | |||||
margin-top: 0; | |||||
text-align: left; | |||||
} | |||||
p.sub { | |||||
font-size: 12px; | |||||
} | |||||
img { | |||||
max-width: 100%; | |||||
} | |||||
/* Layout */ | |||||
.wrapper { | |||||
-premailer-cellpadding: 0; | |||||
-premailer-cellspacing: 0; | |||||
-premailer-width: 100%; | |||||
background-color: #edf2f7; | |||||
margin: 0; | |||||
padding: 0; | |||||
width: 100%; | |||||
} | |||||
.content { | |||||
-premailer-cellpadding: 0; | |||||
-premailer-cellspacing: 0; | |||||
-premailer-width: 100%; | |||||
margin: 0; | |||||
padding: 0; | |||||
width: 100%; | |||||
} | |||||
/* Header */ | |||||
.header { | |||||
padding: 25px 0; | |||||
text-align: center; | |||||
} | |||||
.header a { | |||||
color: #3d4852; | |||||
font-size: 19px; | |||||
font-weight: bold; | |||||
text-decoration: none; | |||||
} | |||||
/* Logo */ | |||||
.logo { | |||||
height: 75px; | |||||
max-height: 75px; | |||||
width: 75px; | |||||
} | |||||
/* Body */ | |||||
.body { | |||||
-premailer-cellpadding: 0; | |||||
-premailer-cellspacing: 0; | |||||
-premailer-width: 100%; | |||||
background-color: #edf2f7; | |||||
border-bottom: 1px solid #edf2f7; | |||||
border-top: 1px solid #edf2f7; | |||||
margin: 0; | |||||
padding: 0; | |||||
width: 100%; | |||||
} | |||||
.inner-body { | |||||
-premailer-cellpadding: 0; | |||||
-premailer-cellspacing: 0; | |||||
-premailer-width: 570px; | |||||
background-color: #ffffff; | |||||
border-color: #e8e5ef; | |||||
border-radius: 2px; | |||||
border-width: 1px; | |||||
box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015); | |||||
margin: 0 auto; | |||||
padding: 0; | |||||
width: 570px; | |||||
} | |||||
/* Subcopy */ | |||||
.subcopy { | |||||
border-top: 1px solid #e8e5ef; | |||||
margin-top: 25px; | |||||
padding-top: 25px; | |||||
} | |||||
.subcopy p { | |||||
font-size: 14px; | |||||
} | |||||
/* Footer */ | |||||
.footer { | |||||
-premailer-cellpadding: 0; | |||||
-premailer-cellspacing: 0; | |||||
-premailer-width: 570px; | |||||
margin: 0 auto; | |||||
padding: 0; | |||||
text-align: center; | |||||
width: 570px; | |||||
} | |||||
.footer p { | |||||
color: #b0adc5; | |||||
font-size: 12px; | |||||
text-align: center; | |||||
} | |||||
.footer a { | |||||
color: #b0adc5; | |||||
text-decoration: underline; | |||||
} | |||||
/* Tables */ | |||||
.table table { | |||||
-premailer-cellpadding: 0; | |||||
-premailer-cellspacing: 0; | |||||
-premailer-width: 100%; | |||||
margin: 30px auto; | |||||
width: 100%; | |||||
} | |||||
.table th { | |||||
border-bottom: 1px solid #edeff2; | |||||
margin: 0; | |||||
padding-bottom: 8px; | |||||
} | |||||
.table td { | |||||
color: #74787e; | |||||
font-size: 15px; | |||||
line-height: 18px; | |||||
margin: 0; | |||||
padding: 10px 0; | |||||
} | |||||
.content-cell { | |||||
max-width: 100vw; | |||||
padding: 32px; | |||||
} | |||||
/* Buttons */ | |||||
.action { | |||||
-premailer-cellpadding: 0; | |||||
-premailer-cellspacing: 0; | |||||
-premailer-width: 100%; | |||||
margin: 30px auto; | |||||
padding: 0; | |||||
text-align: center; | |||||
width: 100%; | |||||
} | |||||
.button { | |||||
-webkit-text-size-adjust: none; | |||||
border-radius: 4px; | |||||
color: #fff; | |||||
display: inline-block; | |||||
overflow: hidden; | |||||
text-decoration: none; | |||||
} | |||||
.button-blue, | |||||
.button-primary { | |||||
background-color: #2d3748; | |||||
border-bottom: 8px solid #2d3748; | |||||
border-left: 18px solid #2d3748; | |||||
border-right: 18px solid #2d3748; | |||||
border-top: 8px solid #2d3748; | |||||
} | |||||
.button-green, | |||||
.button-success { | |||||
background-color: #48bb78; | |||||
border-bottom: 8px solid #48bb78; | |||||
border-left: 18px solid #48bb78; | |||||
border-right: 18px solid #48bb78; | |||||
border-top: 8px solid #48bb78; | |||||
} | |||||
.button-red, | |||||
.button-error { | |||||
background-color: #e53e3e; | |||||
border-bottom: 8px solid #e53e3e; | |||||
border-left: 18px solid #e53e3e; | |||||
border-right: 18px solid #e53e3e; | |||||
border-top: 8px solid #e53e3e; | |||||
} | |||||
/* Panels */ | |||||
.panel { | |||||
border-left: #2d3748 solid 4px; | |||||
margin: 21px 0; | |||||
} | |||||
.panel-content { | |||||
background-color: #edf2f7; | |||||
color: #718096; | |||||
padding: 16px; | |||||
} | |||||
.panel-content p { | |||||
color: #718096; | |||||
} | |||||
.panel-item { | |||||
padding: 0; | |||||
} | |||||
.panel-item p:last-of-type { | |||||
margin-bottom: 0; | |||||
padding-bottom: 0; | |||||
} | |||||
/* Utilities */ | |||||
.break-all { | |||||
word-break: break-all; | |||||
} |
@@ -0,0 +1 @@ | |||||
{{ $slot }}: {{ $url }} |
@@ -0,0 +1 @@ | |||||
{{ $slot }} |
@@ -0,0 +1 @@ | |||||
[{{ $slot }}]({{ $url }}) |
@@ -0,0 +1,9 @@ | |||||
{!! strip_tags($header) !!} | |||||
{!! strip_tags($slot) !!} | |||||
@isset($subcopy) | |||||
{!! strip_tags($subcopy) !!} | |||||
@endisset | |||||
{!! strip_tags($footer) !!} |
@@ -0,0 +1,27 @@ | |||||
@component('mail::layout') | |||||
{{-- Header --}} | |||||
@slot('header') | |||||
@component('mail::header', ['url' => config('app.url')]) | |||||
{{ config('app.name') }} | |||||
@endcomponent | |||||
@endslot | |||||
{{-- Body --}} | |||||
{{ $slot }} | |||||
{{-- Subcopy --}} | |||||
@isset($subcopy) | |||||
@slot('subcopy') | |||||
@component('mail::subcopy') | |||||
{{ $subcopy }} | |||||
@endcomponent | |||||
@endslot | |||||
@endisset | |||||
{{-- Footer --}} | |||||
@slot('footer') | |||||
@component('mail::footer') | |||||
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') | |||||
@endcomponent | |||||
@endslot | |||||
@endcomponent |
@@ -0,0 +1 @@ | |||||
{{ $slot }} |
@@ -0,0 +1 @@ | |||||
{{ $slot }} |
@@ -0,0 +1 @@ | |||||
{{ $slot }} |
@@ -0,0 +1,62 @@ | |||||
@component('mail::message') | |||||
{{-- Greeting --}} | |||||
@if (! empty($greeting)) | |||||
# {{ $greeting }} | |||||
@else | |||||
@if ($level === 'error') | |||||
# @lang('Whoops!') | |||||
@else | |||||
# @lang('Hello!') | |||||
@endif | |||||
@endif | |||||
{{-- Intro Lines --}} | |||||
@foreach ($introLines as $line) | |||||
{{ $line }} | |||||
@endforeach | |||||
{{-- Action Button --}} | |||||
@isset($actionText) | |||||
<?php | |||||
switch ($level) { | |||||
case 'success': | |||||
case 'error': | |||||
$color = $level; | |||||
break; | |||||
default: | |||||
$color = 'primary'; | |||||
} | |||||
?> | |||||
@component('mail::button', ['url' => $actionUrl, 'color' => $color]) | |||||
{{ $actionText }} | |||||
@endcomponent | |||||
@endisset | |||||
{{-- Outro Lines --}} | |||||
@foreach ($outroLines as $line) | |||||
{{ $line }} | |||||
@endforeach | |||||
{{-- Salutation --}} | |||||
@if (! empty($salutation)) | |||||
{{ $salutation }} | |||||
@else | |||||
@lang('Regards'),<br> | |||||
{{ config('app.name') }} | |||||
@endif | |||||
{{-- Subcopy --}} | |||||
@isset($actionText) | |||||
@slot('subcopy') | |||||
@lang( | |||||
"If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\n". | |||||
'into your web browser:', | |||||
[ | |||||
'actionText' => $actionText, | |||||
] | |||||
) <span class="break-all">[{{ $displayableActionUrl }}]({{ $actionUrl }})</span> | |||||
@endslot | |||||
@endisset | |||||
@endcomponent |
@@ -0,0 +1,25 @@ | |||||
@extends('master') | |||||
@section('title', 'Home') | |||||
@section('heading-style', 'info-page') | |||||
@section('head-metas') | |||||
@parent | |||||
<link rel="stylesheet" href="/css/panel.css"> | |||||
@endsection | |||||
@section('content') | |||||
<main> | |||||
<div class="info-heading"> | |||||
<h3>To continue, we need to make sure your email address is yours. Click the verification link we've sent to you.</h3> | |||||
<button id="resend_verification" class="brand-button">Resend</button> | |||||
</div> | |||||
</main> | |||||
@endsection | |||||
@section('scripts') | |||||
@parent | |||||
<script src="js/app.js"></script> | |||||
<script src="js/chunk-vendors.js"></script> | |||||
<!-- <script src="main.js"></script> --> | |||||
@endsection |
@@ -0,0 +1,132 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> | |||||
<head> | |||||
<meta charset="utf-8"> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
<title>Laravel</title> | |||||
<!-- Fonts --> | |||||
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet"> | |||||
<!-- Styles --> | |||||
<style> | |||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}[hidden]{display:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}a{color:inherit;text-decoration:inherit}svg,video{display:block;vertical-align:middle}video{max-width:100%;height:auto}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-t{border-top-width:1px}.flex{display:flex}.grid{display:grid}.hidden{display:none}.items-center{align-items:center}.justify-center{justify-content:center}.font-semibold{font-weight:600}.h-5{height:1.25rem}.h-8{height:2rem}.h-16{height:4rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.leading-7{line-height:1.75rem}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.ml-4{margin-left:1rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-6xl{max-width:72rem}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.p-6{padding:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-8{padding-top:2rem}.fixed{position:fixed}.relative{position:relative}.top-0{top:0}.right-0{right:0}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.text-center{text-align:center}.text-gray-200{--text-opacity:1;color:#edf2f7;color:rgba(237,242,247,var(--text-opacity))}.text-gray-300{--text-opacity:1;color:#e2e8f0;color:rgba(226,232,240,var(--text-opacity))}.text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.w-5{width:1.25rem}.w-8{width:2rem}.w-auto{width:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:h-20{height:5rem}.sm\:ml-0{margin-left:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-left{text-align:left}.sm\:text-right{text-align:right}}@media (min-width:768px){.md\:border-t-0{border-top-width:0}.md\:border-l{border-left-width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.dark\:bg-gray-900{--bg-opacity:1;background-color:#1a202c;background-color:rgba(26,32,44,var(--bg-opacity))}.dark\:border-gray-700{--border-opacity:1;border-color:#4a5568;border-color:rgba(74,85,104,var(--border-opacity))}.dark\:text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.dark\:text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}} | |||||
</style> | |||||
<style> | |||||
body { | |||||
font-family: 'Nunito', sans-serif; | |||||
} | |||||
</style> | |||||
</head> | |||||
<body class="antialiased"> | |||||
<div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center py-4 sm:pt-0"> | |||||
@if (Route::has('login')) | |||||
<div class="hidden fixed top-0 right-0 px-6 py-4 sm:block"> | |||||
@auth | |||||
<a href="{{ url('/home') }}" class="text-sm text-gray-700 underline">Home</a> | |||||
@else | |||||
<a href="{{ route('login') }}" class="text-sm text-gray-700 underline">Log in</a> | |||||
@if (Route::has('register')) | |||||
<a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 underline">Register</a> | |||||
@endif | |||||
@endauth | |||||
</div> | |||||
@endif | |||||
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8"> | |||||
<div class="flex justify-center pt-8 sm:justify-start sm:pt-0"> | |||||
<svg viewBox="0 0 651 192" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-16 w-auto text-gray-700 sm:h-20"> | |||||
<g clip-path="url(#clip0)" fill="#EF3B2D"> | |||||
<path d="M248.032 44.676h-16.466v100.23h47.394v-14.748h-30.928V44.676zM337.091 87.202c-2.101-3.341-5.083-5.965-8.949-7.875-3.865-1.909-7.756-2.864-11.669-2.864-5.062 0-9.69.931-13.89 2.792-4.201 1.861-7.804 4.417-10.811 7.661-3.007 3.246-5.347 6.993-7.016 11.239-1.672 4.249-2.506 8.713-2.506 13.389 0 4.774.834 9.26 2.506 13.459 1.669 4.202 4.009 7.925 7.016 11.169 3.007 3.246 6.609 5.799 10.811 7.66 4.199 1.861 8.828 2.792 13.89 2.792 3.913 0 7.804-.955 11.669-2.863 3.866-1.908 6.849-4.533 8.949-7.875v9.021h15.607V78.182h-15.607v9.02zm-1.431 32.503c-.955 2.578-2.291 4.821-4.009 6.73-1.719 1.91-3.795 3.437-6.229 4.582-2.435 1.146-5.133 1.718-8.091 1.718-2.96 0-5.633-.572-8.019-1.718-2.387-1.146-4.438-2.672-6.156-4.582-1.719-1.909-3.032-4.152-3.938-6.73-.909-2.577-1.36-5.298-1.36-8.161 0-2.864.451-5.585 1.36-8.162.905-2.577 2.219-4.819 3.938-6.729 1.718-1.908 3.77-3.437 6.156-4.582 2.386-1.146 5.059-1.718 8.019-1.718 2.958 0 5.656.572 8.091 1.718 2.434 1.146 4.51 2.674 6.229 4.582 1.718 1.91 3.054 4.152 4.009 6.729.953 2.577 1.432 5.298 1.432 8.162-.001 2.863-.479 5.584-1.432 8.161zM463.954 87.202c-2.101-3.341-5.083-5.965-8.949-7.875-3.865-1.909-7.756-2.864-11.669-2.864-5.062 0-9.69.931-13.89 2.792-4.201 1.861-7.804 4.417-10.811 7.661-3.007 3.246-5.347 6.993-7.016 11.239-1.672 4.249-2.506 8.713-2.506 13.389 0 4.774.834 9.26 2.506 13.459 1.669 4.202 4.009 7.925 7.016 11.169 3.007 3.246 6.609 5.799 10.811 7.66 4.199 1.861 8.828 2.792 13.89 2.792 3.913 0 7.804-.955 11.669-2.863 3.866-1.908 6.849-4.533 8.949-7.875v9.021h15.607V78.182h-15.607v9.02zm-1.432 32.503c-.955 2.578-2.291 4.821-4.009 6.73-1.719 1.91-3.795 3.437-6.229 4.582-2.435 1.146-5.133 1.718-8.091 1.718-2.96 0-5.633-.572-8.019-1.718-2.387-1.146-4.438-2.672-6.156-4.582-1.719-1.909-3.032-4.152-3.938-6.73-.909-2.577-1.36-5.298-1.36-8.161 0-2.864.451-5.585 1.36-8.162.905-2.577 2.219-4.819 3.938-6.729 1.718-1.908 3.77-3.437 6.156-4.582 2.386-1.146 5.059-1.718 8.019-1.718 2.958 0 5.656.572 8.091 1.718 2.434 1.146 4.51 2.674 6.229 4.582 1.718 1.91 3.054 4.152 4.009 6.729.953 2.577 1.432 5.298 1.432 8.162 0 2.863-.479 5.584-1.432 8.161zM650.772 44.676h-15.606v100.23h15.606V44.676zM365.013 144.906h15.607V93.538h26.776V78.182h-42.383v66.724zM542.133 78.182l-19.616 51.096-19.616-51.096h-15.808l25.617 66.724h19.614l25.617-66.724h-15.808zM591.98 76.466c-19.112 0-34.239 15.706-34.239 35.079 0 21.416 14.641 35.079 36.239 35.079 12.088 0 19.806-4.622 29.234-14.688l-10.544-8.158c-.006.008-7.958 10.449-19.832 10.449-13.802 0-19.612-11.127-19.612-16.884h51.777c2.72-22.043-11.772-40.877-33.023-40.877zm-18.713 29.28c.12-1.284 1.917-16.884 18.589-16.884 16.671 0 18.697 15.598 18.813 16.884h-37.402zM184.068 43.892c-.024-.088-.073-.165-.104-.25-.058-.157-.108-.316-.191-.46-.056-.097-.137-.176-.203-.265-.087-.117-.161-.242-.265-.345-.085-.086-.194-.148-.29-.223-.109-.085-.206-.182-.327-.252l-.002-.001-.002-.002-35.648-20.524a2.971 2.971 0 00-2.964 0l-35.647 20.522-.002.002-.002.001c-.121.07-.219.167-.327.252-.096.075-.205.138-.29.223-.103.103-.178.228-.265.345-.066.089-.147.169-.203.265-.083.144-.133.304-.191.46-.031.085-.08.162-.104.25-.067.249-.103.51-.103.776v38.979l-29.706 17.103V24.493a3 3 0 00-.103-.776c-.024-.088-.073-.165-.104-.25-.058-.157-.108-.316-.191-.46-.056-.097-.137-.176-.203-.265-.087-.117-.161-.242-.265-.345-.085-.086-.194-.148-.29-.223-.109-.085-.206-.182-.327-.252l-.002-.001-.002-.002L40.098 1.396a2.971 2.971 0 00-2.964 0L1.487 21.919l-.002.002-.002.001c-.121.07-.219.167-.327.252-.096.075-.205.138-.29.223-.103.103-.178.228-.265.345-.066.089-.147.169-.203.265-.083.144-.133.304-.191.46-.031.085-.08.162-.104.25-.067.249-.103.51-.103.776v122.09c0 1.063.568 2.044 1.489 2.575l71.293 41.045c.156.089.324.143.49.202.078.028.15.074.23.095a2.98 2.98 0 001.524 0c.069-.018.132-.059.2-.083.176-.061.354-.119.519-.214l71.293-41.045a2.971 2.971 0 001.489-2.575v-38.979l34.158-19.666a2.971 2.971 0 001.489-2.575V44.666a3.075 3.075 0 00-.106-.774zM74.255 143.167l-29.648-16.779 31.136-17.926.001-.001 34.164-19.669 29.674 17.084-21.772 12.428-43.555 24.863zm68.329-76.259v33.841l-12.475-7.182-17.231-9.92V49.806l12.475 7.182 17.231 9.92zm2.97-39.335l29.693 17.095-29.693 17.095-29.693-17.095 29.693-17.095zM54.06 114.089l-12.475 7.182V46.733l17.231-9.92 12.475-7.182v74.537l-17.231 9.921zM38.614 7.398l29.693 17.095-29.693 17.095L8.921 24.493 38.614 7.398zM5.938 29.632l12.475 7.182 17.231 9.92v79.676l.001.005-.001.006c0 .114.032.221.045.333.017.146.021.294.059.434l.002.007c.032.117.094.222.14.334.051.124.088.255.156.371a.036.036 0 00.004.009c.061.105.149.191.222.288.081.105.149.22.244.314l.008.01c.084.083.19.142.284.215.106.083.202.178.32.247l.013.005.011.008 34.139 19.321v34.175L5.939 144.867V29.632h-.001zm136.646 115.235l-65.352 37.625V148.31l48.399-27.628 16.953-9.677v33.862zm35.646-61.22l-29.706 17.102V66.908l17.231-9.92 12.475-7.182v33.841z"/> | |||||
</g> | |||||
</svg> | |||||
</div> | |||||
<div class="mt-8 bg-white dark:bg-gray-800 overflow-hidden shadow sm:rounded-lg"> | |||||
<div class="grid grid-cols-1 md:grid-cols-2"> | |||||
<div class="p-6"> | |||||
<div class="flex items-center"> | |||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg> | |||||
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laravel.com/docs" class="underline text-gray-900 dark:text-white">Documentation</a></div> | |||||
</div> | |||||
<div class="ml-12"> | |||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm"> | |||||
Laravel has wonderful, thorough documentation covering every aspect of the framework. Whether you are new to the framework or have previous experience with Laravel, we recommend reading all of the documentation from beginning to end. | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="p-6 border-t border-gray-200 dark:border-gray-700 md:border-t-0 md:border-l"> | |||||
<div class="flex items-center"> | |||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path><path d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path></svg> | |||||
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laracasts.com" class="underline text-gray-900 dark:text-white">Laracasts</a></div> | |||||
</div> | |||||
<div class="ml-12"> | |||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm"> | |||||
Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process. | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="p-6 border-t border-gray-200 dark:border-gray-700"> | |||||
<div class="flex items-center"> | |||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"></path></svg> | |||||
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laravel-news.com/" class="underline text-gray-900 dark:text-white">Laravel News</a></div> | |||||
</div> | |||||
<div class="ml-12"> | |||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm"> | |||||
Laravel News is a community driven portal and newsletter aggregating all of the latest and most important news in the Laravel ecosystem, including new package releases and tutorials. | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="p-6 border-t border-gray-200 dark:border-gray-700 md:border-l"> | |||||
<div class="flex items-center"> | |||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> | |||||
<div class="ml-4 text-lg leading-7 font-semibold text-gray-900 dark:text-white">Vibrant Ecosystem</div> | |||||
</div> | |||||
<div class="ml-12"> | |||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm"> | |||||
Laravel's robust library of first-party tools and libraries, such as <a href="https://forge.laravel.com" class="underline">Forge</a>, <a href="https://vapor.laravel.com" class="underline">Vapor</a>, <a href="https://nova.laravel.com" class="underline">Nova</a>, and <a href="https://envoyer.io" class="underline">Envoyer</a> help you take your projects to the next level. Pair them with powerful open source libraries like <a href="https://laravel.com/docs/billing" class="underline">Cashier</a>, <a href="https://laravel.com/docs/dusk" class="underline">Dusk</a>, <a href="https://laravel.com/docs/broadcasting" class="underline">Echo</a>, <a href="https://laravel.com/docs/horizon" class="underline">Horizon</a>, <a href="https://laravel.com/docs/sanctum" class="underline">Sanctum</a>, <a href="https://laravel.com/docs/telescope" class="underline">Telescope</a>, and more. | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="flex justify-center mt-4 sm:items-center sm:justify-between"> | |||||
<div class="text-center text-sm text-gray-500 sm:text-left"> | |||||
<div class="flex items-center"> | |||||
<svg fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor" class="-mt-px w-5 h-5 text-gray-400"> | |||||
<path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path> | |||||
</svg> | |||||
<a href="https://laravel.bigcartel.com" class="ml-1 underline"> | |||||
Shop | |||||
</a> | |||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="ml-4 -mt-px w-5 h-5 text-gray-400"> | |||||
<path d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path> | |||||
</svg> | |||||
<a href="https://github.com/sponsors/taylorotwell" class="ml-1 underline"> | |||||
Sponsor | |||||
</a> | |||||
</div> | |||||
</div> | |||||
<div class="ml-4 text-center text-sm text-gray-500 sm:text-right sm:ml-0"> | |||||
Laravel v{{ Illuminate\Foundation\Application::VERSION }} (PHP v{{ PHP_VERSION }}) | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</body> | |||||
</html> |