diff --git a/README.md b/README.md index c2a92ae..ad34d2a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Code Examples 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. diff --git a/view.go b/go/view.go similarity index 100% rename from view.go rename to go/view.go diff --git a/php-laravel/app/Console/Commands/ServicesInit.php b/php-laravel/app/Console/Commands/ServicesInit.php new file mode 100644 index 0000000..b5c8f21 --- /dev/null +++ b/php-laravel/app/Console/Commands/ServicesInit.php @@ -0,0 +1,300 @@ +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(); + + } + +} + diff --git a/php-laravel/app/Console/Kernel.php b/php-laravel/app/Console/Kernel.php new file mode 100644 index 0000000..6828607 --- /dev/null +++ b/php-laravel/app/Console/Kernel.php @@ -0,0 +1,41 @@ +command('inspire')->hourly(); + } + + /** + * Register the commands for the application. + * + * @return void + */ + protected function commands() + { + $this->load(__DIR__.'/Commands'); + + require base_path('routes/console.php'); + } +} diff --git a/php-laravel/app/Exceptions/Handler.php b/php-laravel/app/Exceptions/Handler.php new file mode 100644 index 0000000..c18c43c --- /dev/null +++ b/php-laravel/app/Exceptions/Handler.php @@ -0,0 +1,41 @@ +reportable(function (Throwable $e) { + // + }); + } +} diff --git a/BillingController.php b/php-laravel/app/Http/Controllers/BillingController.php similarity index 100% rename from BillingController.php rename to php-laravel/app/Http/Controllers/BillingController.php diff --git a/php-laravel/app/Http/Controllers/Controller.php b/php-laravel/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..a0a2a8a --- /dev/null +++ b/php-laravel/app/Http/Controllers/Controller.php @@ -0,0 +1,13 @@ +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(); + } +} diff --git a/php-laravel/app/Http/Controllers/ServiceController.php b/php-laravel/app/Http/Controllers/ServiceController.php new file mode 100644 index 0000000..d1ee7b2 --- /dev/null +++ b/php-laravel/app/Http/Controllers/ServiceController.php @@ -0,0 +1,14 @@ + + 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; + } + + +} diff --git a/php-laravel/app/Http/Controllers/Ticket.php b/php-laravel/app/Http/Controllers/Ticket.php new file mode 100644 index 0000000..4a723cd --- /dev/null +++ b/php-laravel/app/Http/Controllers/Ticket.php @@ -0,0 +1,38 @@ +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; + } +} diff --git a/php-laravel/app/Http/Controllers/UserController.php b/php-laravel/app/Http/Controllers/UserController.php new file mode 100644 index 0000000..749782b --- /dev/null +++ b/php-laravel/app/Http/Controllers/UserController.php @@ -0,0 +1,165 @@ +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(); + } + } + +} diff --git a/php-laravel/app/Http/Kernel.php b/php-laravel/app/Http/Kernel.php new file mode 100644 index 0000000..a2383b0 --- /dev/null +++ b/php-laravel/app/Http/Kernel.php @@ -0,0 +1,67 @@ + [ + \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, + ]; +} diff --git a/php-laravel/app/Http/Middleware/Authenticate.php b/php-laravel/app/Http/Middleware/Authenticate.php new file mode 100644 index 0000000..704089a --- /dev/null +++ b/php-laravel/app/Http/Middleware/Authenticate.php @@ -0,0 +1,21 @@ +expectsJson()) { + return route('login'); + } + } +} diff --git a/php-laravel/app/Http/Middleware/EncryptCookies.php b/php-laravel/app/Http/Middleware/EncryptCookies.php new file mode 100644 index 0000000..033136a --- /dev/null +++ b/php-laravel/app/Http/Middleware/EncryptCookies.php @@ -0,0 +1,17 @@ +check()) { + return redirect(RouteServiceProvider::HOME); + } + } + + return $next($request); + } +} diff --git a/php-laravel/app/Http/Middleware/TrimStrings.php b/php-laravel/app/Http/Middleware/TrimStrings.php new file mode 100644 index 0000000..a8a252d --- /dev/null +++ b/php-laravel/app/Http/Middleware/TrimStrings.php @@ -0,0 +1,19 @@ +allSubdomainsOfApplicationUrl(), + ]; + } +} diff --git a/php-laravel/app/Http/Middleware/TrustProxies.php b/php-laravel/app/Http/Middleware/TrustProxies.php new file mode 100644 index 0000000..a3b6aef --- /dev/null +++ b/php-laravel/app/Http/Middleware/TrustProxies.php @@ -0,0 +1,23 @@ +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; + } +} diff --git a/php-laravel/app/Mail/SupportTicket.php b/php-laravel/app/Mail/SupportTicket.php new file mode 100644 index 0000000..4d52988 --- /dev/null +++ b/php-laravel/app/Mail/SupportTicket.php @@ -0,0 +1,49 @@ +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; + } +} diff --git a/php-laravel/app/Models/Order.php b/php-laravel/app/Models/Order.php new file mode 100644 index 0000000..0dcc659 --- /dev/null +++ b/php-laravel/app/Models/Order.php @@ -0,0 +1,30 @@ +belongsTo(Service::class); + } + + public function user() { + return $this->belongsTo(User::class); + } + + public function getServiceNameAttribute() { + return $this->service()->name(); + } + +} diff --git a/php-laravel/app/Models/Referral.php b/php-laravel/app/Models/Referral.php new file mode 100644 index 0000000..8475a1b --- /dev/null +++ b/php-laravel/app/Models/Referral.php @@ -0,0 +1,17 @@ +hasMany(User::class); + } +} diff --git a/php-laravel/app/Models/Service.php b/php-laravel/app/Models/Service.php new file mode 100644 index 0000000..cffe180 --- /dev/null +++ b/php-laravel/app/Models/Service.php @@ -0,0 +1,30 @@ +hasMany(Order::class); + } + + public function suppliers() { + return $this->hasMany(Supplier::class); + } + + public function primary() { + return $this->belongsTo(Supplier::class, 'primary_supplier', 'id'); + } + +} diff --git a/php-laravel/app/Models/Supplier.php b/php-laravel/app/Models/Supplier.php new file mode 100644 index 0000000..45e784d --- /dev/null +++ b/php-laravel/app/Models/Supplier.php @@ -0,0 +1,18 @@ +belongsTo(Service::class); + } +} + diff --git a/php-laravel/app/Models/Ticket.php b/php-laravel/app/Models/Ticket.php new file mode 100644 index 0000000..964dcad --- /dev/null +++ b/php-laravel/app/Models/Ticket.php @@ -0,0 +1,16 @@ +belongsTo(User::class); + } +} diff --git a/php-laravel/app/Models/Transaction.php b/php-laravel/app/Models/Transaction.php new file mode 100644 index 0000000..4725f7c --- /dev/null +++ b/php-laravel/app/Models/Transaction.php @@ -0,0 +1,16 @@ +belongsTo(User::class); + } +} diff --git a/php-laravel/app/Models/User.php b/php-laravel/app/Models/User.php new file mode 100644 index 0000000..94f19d3 --- /dev/null +++ b/php-laravel/app/Models/User.php @@ -0,0 +1,60 @@ + '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); + } + +} diff --git a/php-laravel/app/Providers/AppServiceProvider.php b/php-laravel/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..ee8ca5b --- /dev/null +++ b/php-laravel/app/Providers/AppServiceProvider.php @@ -0,0 +1,28 @@ + 'App\Policies\ModelPolicy', + ]; + + /** + * Register any authentication / authorization services. + * + * @return void + */ + public function boot() + { + $this->registerPolicies(); + + // + } +} diff --git a/php-laravel/app/Providers/BroadcastServiceProvider.php b/php-laravel/app/Providers/BroadcastServiceProvider.php new file mode 100644 index 0000000..395c518 --- /dev/null +++ b/php-laravel/app/Providers/BroadcastServiceProvider.php @@ -0,0 +1,21 @@ + [ + SendEmailVerificationNotification::class, + ], + ]; + + /** + * Register any events for your application. + * + * @return void + */ + public function boot() + { + // + } +} diff --git a/php-laravel/app/Providers/RouteServiceProvider.php b/php-laravel/app/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..3bd3c81 --- /dev/null +++ b/php-laravel/app/Providers/RouteServiceProvider.php @@ -0,0 +1,63 @@ +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()); + }); + } +} diff --git a/php-laravel/js/app.js b/php-laravel/js/app.js new file mode 100644 index 0000000..40c55f6 --- /dev/null +++ b/php-laravel/js/app.js @@ -0,0 +1 @@ +require('./bootstrap'); diff --git a/php-laravel/js/bootstrap.js b/php-laravel/js/bootstrap.js new file mode 100644 index 0000000..6922577 --- /dev/null +++ b/php-laravel/js/bootstrap.js @@ -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 +// }); diff --git a/php-laravel/js/icons/eye-fill.vue b/php-laravel/js/icons/eye-fill.vue new file mode 100644 index 0000000..726eed7 --- /dev/null +++ b/php-laravel/js/icons/eye-fill.vue @@ -0,0 +1,8 @@ + diff --git a/php-laravel/js/icons/instagram.vue b/php-laravel/js/icons/instagram.vue new file mode 100644 index 0000000..cd4ec42 --- /dev/null +++ b/php-laravel/js/icons/instagram.vue @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/php-laravel/js/icons/loading.vue b/php-laravel/js/icons/loading.vue new file mode 100644 index 0000000..10fd5d3 --- /dev/null +++ b/php-laravel/js/icons/loading.vue @@ -0,0 +1,3 @@ + diff --git a/php-laravel/js/icons/plus-fill.vue b/php-laravel/js/icons/plus-fill.vue new file mode 100644 index 0000000..4a86388 --- /dev/null +++ b/php-laravel/js/icons/plus-fill.vue @@ -0,0 +1,5 @@ + diff --git a/php-laravel/js/icons/plus.vue b/php-laravel/js/icons/plus.vue new file mode 100644 index 0000000..b917ed6 --- /dev/null +++ b/php-laravel/js/icons/plus.vue @@ -0,0 +1,6 @@ + diff --git a/php-laravel/js/icons/youtube.vue b/php-laravel/js/icons/youtube.vue new file mode 100644 index 0000000..1bf5548 --- /dev/null +++ b/php-laravel/js/icons/youtube.vue @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/php-laravel/js/main.js b/php-laravel/js/main.js new file mode 100644 index 0000000..4ba0b70 --- /dev/null +++ b/php-laravel/js/main.js @@ -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} +} + diff --git a/php-laravel/js/panel/admin.vue b/php-laravel/js/panel/admin.vue new file mode 100644 index 0000000..e69de29 diff --git a/php-laravel/js/panel/credits.vue b/php-laravel/js/panel/credits.vue new file mode 100644 index 0000000..90e1fc0 --- /dev/null +++ b/php-laravel/js/panel/credits.vue @@ -0,0 +1,188 @@ + + + diff --git a/php-laravel/js/panel/edit-cards.vue b/php-laravel/js/panel/edit-cards.vue new file mode 100644 index 0000000..31bf0ac --- /dev/null +++ b/php-laravel/js/panel/edit-cards.vue @@ -0,0 +1,82 @@ + + + diff --git a/php-laravel/js/panel/order-item.vue b/php-laravel/js/panel/order-item.vue new file mode 100644 index 0000000..171dad3 --- /dev/null +++ b/php-laravel/js/panel/order-item.vue @@ -0,0 +1,71 @@ + + + diff --git a/php-laravel/js/panel/orders.vue b/php-laravel/js/panel/orders.vue new file mode 100644 index 0000000..c743c09 --- /dev/null +++ b/php-laravel/js/panel/orders.vue @@ -0,0 +1,100 @@ + + + diff --git a/php-laravel/js/panel/panel.vue b/php-laravel/js/panel/panel.vue new file mode 100644 index 0000000..3dcde3c --- /dev/null +++ b/php-laravel/js/panel/panel.vue @@ -0,0 +1,130 @@ + + + diff --git a/php-laravel/js/panel/payment-card.vue b/php-laravel/js/panel/payment-card.vue new file mode 100644 index 0000000..7bdb90a --- /dev/null +++ b/php-laravel/js/panel/payment-card.vue @@ -0,0 +1,42 @@ + + + diff --git a/php-laravel/js/panel/payment-slider.vue b/php-laravel/js/panel/payment-slider.vue new file mode 100644 index 0000000..cba5483 --- /dev/null +++ b/php-laravel/js/panel/payment-slider.vue @@ -0,0 +1,17 @@ + + + diff --git a/php-laravel/js/panel/saved-cards.vue b/php-laravel/js/panel/saved-cards.vue new file mode 100644 index 0000000..3a6a6dc --- /dev/null +++ b/php-laravel/js/panel/saved-cards.vue @@ -0,0 +1,28 @@ + + + diff --git a/php-laravel/js/panel/service-pane.vue b/php-laravel/js/panel/service-pane.vue new file mode 100644 index 0000000..c96b1ab --- /dev/null +++ b/php-laravel/js/panel/service-pane.vue @@ -0,0 +1,28 @@ + + + diff --git a/php-laravel/js/panel/services.vue b/php-laravel/js/panel/services.vue new file mode 100644 index 0000000..899db6d --- /dev/null +++ b/php-laravel/js/panel/services.vue @@ -0,0 +1,196 @@ + + + diff --git a/php-laravel/js/panel/settings.vue b/php-laravel/js/panel/settings.vue new file mode 100644 index 0000000..21df833 --- /dev/null +++ b/php-laravel/js/panel/settings.vue @@ -0,0 +1,119 @@ + + + diff --git a/php-laravel/js/panel/sidebar.vue b/php-laravel/js/panel/sidebar.vue new file mode 100644 index 0000000..4404fdb --- /dev/null +++ b/php-laravel/js/panel/sidebar.vue @@ -0,0 +1,46 @@ + + + + diff --git a/php-laravel/js/panel/summary.vue b/php-laravel/js/panel/summary.vue new file mode 100644 index 0000000..9a8bdf5 --- /dev/null +++ b/php-laravel/js/panel/summary.vue @@ -0,0 +1,3 @@ + diff --git a/php-laravel/js/panel/support.vue b/php-laravel/js/panel/support.vue new file mode 100644 index 0000000..b9af205 --- /dev/null +++ b/php-laravel/js/panel/support.vue @@ -0,0 +1,82 @@ + + + + diff --git a/php-laravel/js/panel/transaction-endpoint.vue b/php-laravel/js/panel/transaction-endpoint.vue new file mode 100644 index 0000000..68d6da6 --- /dev/null +++ b/php-laravel/js/panel/transaction-endpoint.vue @@ -0,0 +1,33 @@ + + + diff --git a/php-laravel/js/register-area/register-area.vue b/php-laravel/js/register-area/register-area.vue new file mode 100644 index 0000000..311fa3a --- /dev/null +++ b/php-laravel/js/register-area/register-area.vue @@ -0,0 +1,168 @@ + + + diff --git a/php-laravel/routes/api.php b/php-laravel/routes/api.php new file mode 100644 index 0000000..bcb8b18 --- /dev/null +++ b/php-laravel/routes/api.php @@ -0,0 +1,19 @@ +get('/user', function (Request $request) { + return $request->user(); +}); diff --git a/php-laravel/routes/channels.php b/php-laravel/routes/channels.php new file mode 100644 index 0000000..5d451e1 --- /dev/null +++ b/php-laravel/routes/channels.php @@ -0,0 +1,18 @@ +id === (int) $id; +}); diff --git a/php-laravel/routes/console.php b/php-laravel/routes/console.php new file mode 100644 index 0000000..e05f4c9 --- /dev/null +++ b/php-laravel/routes/console.php @@ -0,0 +1,19 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote'); diff --git a/php-laravel/routes/web.php b/php-laravel/routes/web.php new file mode 100644 index 0000000..03a4087 --- /dev/null +++ b/php-laravel/routes/web.php @@ -0,0 +1,162 @@ +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']); diff --git a/php-laravel/scss/_vars.scss b/php-laravel/scss/_vars.scss new file mode 100644 index 0000000..ad417c6 --- /dev/null +++ b/php-laravel/scss/_vars.scss @@ -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; +} diff --git a/php-laravel/scss/main.scss b/php-laravel/scss/main.scss new file mode 100644 index 0000000..a3b5bbd --- /dev/null +++ b/php-laravel/scss/main.scss @@ -0,0 +1,1454 @@ +@use "vars"; + +@font-face { + font-family: "PatuaOne"; + src: url("../PatuaOne-Regular.ttf") format("opentype"); + font-style: normal; + font-weight: normal; +} + +html body { + font-family: 'PatuaOne', 'Times New Roman'; + margin: 0; +} + +section { + position: relative; + padding-top: 3em; + padding-bottom: 3em; + overflow: hidden; + padding-right: 15px; + padding-left: 15px; +} + +button { + font-family: 'PatuaOne', 'Times New Roman'; +} + +textarea { + border: 1px solid grey; +} + +h1 { + color: white; +} + +h2 { + color: vars.getColor("brand-orange"); + text-align: center; +} + +h3 { + color: vars.getColor("faded-text"); + // text-transform: uppercase; +} + +ul { + color: vars.getColor("faded-text"); + list-style-type: none; + padding: 0; +} + +input, select { + border: 2px solid black; + border-radius: 20px; + padding: 4px; +} + +.icon { + width: 25px; + margin: 10px; +} + +.note-grey { + color: grey; + font-size: 0.8em; + max-width: 70%; +} + +.services-cards li { + padding-bottom: 8px; + &:before { + width: 30px; + height: 30px; + content: url("../images/green-check.svg"); + display: inline; + margin-right: 5px; + // top: 30px; + } +} + +a { + text-decoration: none; +} + +nav { + position: absolute; + width: 100%; + top: 10px; + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: space-between; + // background-color: transparent; +} + +nav h1 { + margin: 4px 0 10px 6px; + display: inline-block; +} + +nav.info-page h1 { + margin: 4px 0 10px 6px; + display: inline-block; + color: vars.getColor('brand-orange'); +} + +.formal-font { + font-family: "FreeSans"; +} + +nav #logo { + margin-right: 50px; +} + +nav form { + display: flex; + flex-wrap: wrap; + margin-right: 20px; +} + +nav form div { + margin: 0 10px; +} + +nav form button { + min-width: 5em; +} + +nav form p { + margin-top: 0; +} + +nav form input,label { + opacity: 0.7; +} + +nav .nav-toggle { + position: absolute; + right: 10px; + top: 5px; + display: none; + height: 45px; + width: 45px; + margin-left: auto; + background: url("../images/menu-icon.svg"); + background-size: 100%; + background-size: cover; + color: white; +} + +nav .nav-toggle.toggled { + background: url("../images/cancel-icon2.svg"); + width: 25px; + height: 25px; + background-size: 100%; + background-size: cover; + top: 10px; + right: 15px; + color: white; +} + +div.landing-hero { + top: 0; + height: 50em; + background-color: #ff4e00; + // background-image: linear-gradient(to right bottom, #ff4e00, #fc6200, #f87200, #f58100, #f18e00, #f19507, #f09c0f, #f0a317, #f3a620, #f6a927, #f8ad2e, #fbb034); + background-image: linear-gradient(315deg, #ff4e00 0%, #ec9f05 74%); + z-index: 0; +} + +div.hero-filter { + width: 100%; + height: 100%; +} + +div.hero-filter h2 { + color: black; + opacity: 0.7; + max-width: 10em; + font-size: 2.4em; + position: absolute; + top: 3em; + margin-left: 5%; +} + +div.hero-filter p { + position: absolute; + top: 12em; + margin-left: 5%; + font-size: 20px; + width: 10em; + opacity: 0.7; +} + +div.errors { + background-color: #ececec; + color: vars.getColor("red-alert"); + border: 2px solid vars.getColor("red-alert"); + border-radius: 4px; + min-height: 50px; + position: absolute; + left: 50%; + margin-right: -50%; + top: 100px; + min-width: 30em; + transform: translate(-50%, -50%); +} + +.errors ul { + margin: auto; +} + +form.login { + // font-weight: bold; + color: white; +} + +.cancel-icon { + width: 30px; +} + +form.login input { + margin-left: 2px; + color: white; + background-color: transparent; + border: 2px solid white; + border-radius: 2px; + outline-color: orange +} + +form.login .login-btn { + @include vars.special-button("medium-blue", "dark-blue"); +} + +.hero-filter .register-btn { + @include vars.transparent-button; + display: block; + position: relative; + width: 7em; + top: 60%; + margin-left: auto; + margin-right: auto; +} + +div.blue-background { + background-image: url("../images/black-circle.svg"); + background-size: 10px; + background-color: #0d324d; +} + +section.about-us { + padding: 70px 0; +} + +section.about-us p { + max-width: 600px; + margin-left: auto; + margin-right: auto; + border-top: 2px solid vars.getColor("grey"); + border-bottom: 2px solid vars.getColor("grey"); + padding: 15px; + background-color: #0d324d; + color: white; +} + +section.services-cards { + // margin-bottom: 0; + background-color: lightgrey; + padding-bottom: 4em; + background: linear-gradient(to bottom right, transparent 0%, transparent 50%, #e9e9e9 50%, vars.getColor("light-grey") 100%); +} +section.services-cards .cards{ + display: flex; + flex-flow: wrap; + margin-top: 2em; + gap: 25px; + justify-content: center; + max-width: 1050px; + margin-left: auto; + margin-right: auto; + text-align: center; +} + +section.services-cards h2 { + text-align: center; + padding: 10px; + margin-top: 0; + padding-top: 30px; + color: white; +} + +section.services-cards .card { + width: 280px; + height: 350px; + padding: 18px 9px; + border-radius: 4px; + background: white; + box-shadow: 7px 10px 8px rgb(156 166 175 / 22%); + transition: transform 0.2s; + &:hover { + transform: translateY(-20px); + } + +} + +section.services-cards img { + width: 50px; + height: 50px; +} + +section.services-cards button { + @include vars.brand-button("orange"); +} + +section.features-info { + // height: fit-content; + height: 70em; + background: radial-gradient(ellipse at left, #fff, vars.getColor("light-grey") 80%); +} + +.circle-prop { + height: 70em; + overflow: hidden; + width: 100%; + position: relative; + // transform: translateX(-10em); +} + +.circle-prop svg { + width: 50%; +} + +.features-info img { + width: 600px; + position: absolute; + top: 15%; + right: 4%; +} + +div.info-cards { + position: absolute; + top: 10em; + margin-left: 20%; + width: 70%; +} + +.info-card { + margin-bottom: 6em; + + &:nth-of-type(2) { + margin-left: 13%; + } + + &:nth-of-type(3) { + margin-left: 10%; + } + + &:nth-of-type(4) { + margin-left: 5%; + } + + p { + max-width: 20em; + background: white; + padding: 5px; + border-radius: 4px; + text-align: center; + color: vars.getColor("faded-text2"); + // border: 2px solid vars.getColor("medium-orange"); + @include vars.hovering; + + } +} + +section.panel-infos { + min-height: 40em; + margin-top: 5em; + margin-bottom: 5em; + display: flex; + flex-flow: wrap; + align-items: center; + justify-content: center; + gap: 20px; + + .panel-preview { + // box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px; + @include vars.hovering; + padding: 0; + } +} + +.panel-infos h2 { + width: 100%; +} + + +.panel-info { + max-width: 25em; + margin-top: 4em; +} + +.panel-info img { + width: 50px; + height: 50px; +} + +.panel-info p { + color: vars.getColor("faded-text2"); +} + +section.benefits-info div { + margin: 1em auto; + padding: 1em 1em; + border-top: 2px solid vars.getColor("faded-text2"); + border-bottom: 2px solid vars.getColor("faded-text2"); + max-width: 40em; + color: vars.getColor("faded-text"); +} + +.benefits-info h2 { + margin-bottom: 2em; +} + +section.faq-info { + margin-bottom: 6em; + margin-top: 3em; + min-height: 30em; +} + +.faq-info h2 { + margin-bottom: 2em; +} + +section.faq-info .collapsibles { + display: flex; + flex-flow: wrap; + gap: 2em; + justify-content: center; + max-width: 65em; + width: fit-content; + margin-left: auto; + margin-right: auto; + align-text: center; +} + +.faq-info .collapsible { + text-align: center; + width: 20em; + &:not(.active) .content { + display: none; + } +} + +.collapsible button { + width: 95%; + display: block; + margin-right: auto; + height: 2.5em; + border: 2px solid vars.getColor("brand-orange"); + @include vars.button("light-grey"); + font-size: 17px; + // font-weight: bold; +} + +.collapsible button:after { + content: '\02795'; /* Unicode character for "plus" sign (+) */ + font-size: 13px; + color: white; + float: right; + margin-left: 5px; +} + + +.collapsible .content { + // background-color: vars.getColor("faded-text2"); + border: 2px solid black; + border-radius: 4px; + padding: 4px; + // transition: all 0.2s; + // height: 1px; +} + +footer { + height: 6em; + background-color: vars.getColor("dark-grey"); + padding: 20px; + color: vars.getColor("faded-text2"); +} + +footer .foot-links { + width: 20em; + margin: 2em auto; + text-align: center; +} + +footer a { + margin: 0 8px; + color: vars.getColor("faded-text2"); + &:hover { + color: vars.getColor("faded-text"); + } +} + +.hidden { + display: none; +} + +.loading-icon { + display: block; + width: 50px; + height: 50px; + margin: auto; +} + +.register-area p { + text-align: center; +} + +.register-area .loading-icon { + margin-top: 100px; +} + +div.register-area { + overflow: hidden; + height: 0px; + width: 100%; + z-index: 1; + @include vars.hovering3; + margin-bottom: 2em; + position: fixed; + border-radius: 0; + padding: 0; + opacity: 0; + transition: all 0.2s; + background: white; + h3 { + text-align: center; + } + + label,input { + color: black; + display: block; + border-color: black; + width: 95%; + } + + div { + margin-top: 20px; + margin-bottom: 20px; + width: 100%; + } + + form { + width: 160px; + margin: 20px auto; + } + + .submit-btn { + width: 100%; + background-color: #3bb78f; + @include vars.special-button("light-green", "dark-green"); + } + + .cancel-button { + width: 20px; + height: 20px; + background: url("../images/close-icon-black.svg"); + background-size: cover; + position: absolute; + top: 20px; + right: 20px; + } +} + +section.features-info { + // min-height: 90em; +} + +div.register-area.active { + height: fit-content; + min-height: 20em; + opacity: 1; +} + +.medium-icon { + width: 50px; + height: 50px; + margin-left: auto; + margin-right: auto; + display: block; + top: 50px; +} + +// Make small screens more usable +@media (max-width: 720px) { + nav { display: block; } + + nav form { + // display: none; + width: 190px; + margin-left: auto; + margin-right: auto; + align-items: center; + margin-top: 5em; + opacity: 0; + transition: transform 0.3s; + transform: translateY(-50px); + } + + nav form.active { + display: block; + transform: translateX(0%); + opacity: 1; + // background: white; + // color: black; + } + + nav form div { + margin: 10px auto; + } + + nav div label,input { + display: block; + } + + nav .nav-toggle { + display: block; + } + + div.register-area.active { + display: initial; + } + + div.register-area h1 { + font-size: 1.5rem; + display: inline-block; + color: vars.getColor("brand-orange"); + } + + div.register-area h3 { + text-align: center; + } + + div.register-area .login { + color: vars.getColor("brand-orange"); + display: block; + margin-left: auto; + margin-right: auto; + // margin-top: 1em; + width: 10em; + // top: 40px; + // position: absolute; + } + + div.register-area .login input { + border: 2px solid black; + border-color: black; + color: black; + } + + div.register-area button { + margin-top: 1em; + width: 6em; + } + + div.register-area div { + margin-top: 10px; + margin-bottom: 5px; + margin-left: auto; + margin-right: auto; + } + + .landing-hero div.hero-filter h2 { + top: 4em; + } + + .landing-hero div.hero-filter p { + top: 15em; + } + + .landing-hero div.hero-filter .register-btn { + top: 75%; + } +} + +.info-heading { + text-align: center; + margin-top: 3em; + margin-left: auto; + margin-right: auto; + max-width: 35em; + padding: 3em; + height: 30em; + button { + display: block; + margin: 3em auto; + width: 6em; + @include vars.special-button("light-green", "dark-green"); + } +} + +main.panel { + // min-height: 750px; + height: 100vh; + // max-height: 100vh; + background-image: linear-gradient(315deg, #ff4e00 0%, #ec9f05 74%); +} + +#panel { + top: 50px; + padding: 0 5%; + margin: auto; + position: relative; + max-width: 800px; + height: 90%; + display: flex; + gap: 10px; +} + +#sidebar { + position: relative; + width: 6%; + max-width: 55px; + height: 50%; + color: white; + background: transparent; + display: block; + flex-shrink: 0; + + a { + display: block; + margin: 20px 0; + } + + svg { + width: 100%; + height: 35px; + color: white; + } + + a.selected svg { + color: vars.getColor("brand-orange"); + } +} + +#panel #main { + background: white; + width: 100%; + flex-shrink: 1; + margin-left: auto; + position: relative; + @include vars.hovering3; + transition: opacity 0.5s ease; + vertical-align: middle; + overflow: scroll; + // text-align: center; + + section { + padding: 5px; + border-radius: 4px; + margin-bottom: 2em; + } + + h4 { + margin: 4px; + color: vars.getColor("dark-grey"); + } + + .welcome-pane { + text-align: center; + } + + .credits-pane { + border: 3px solid vars.getColor("orange"); + display: flex; + align-items: center; + justify-content: center; + color: vars.getColor('brand-orange'); + } + + .alerts-pane { + border: 3px solid vars.getColor("grey"); + padding: 1em; + } +} + +.fade-enter-from, .fade-leave-to { + opacity: 0; +} + +section.recent-pane, section.history-pane{ + display: flex; + flex-flow: wrap; + justify-content: space-between; + width: 100%; + height: fit-content; + + h4 { + width: 100%; + } + + table { + border-spacing: 4px; + margin: auto; + text-align: center; + width: 100%; + } + + th { + font-weight: normal; + color: vars.getColor("light2-grey"); + } + + tr { + border-bottom: 3px solid black; + } + + td { + border-bottom: 2px solid vars.getColor("grey"); + // color: vars.getColor("light3-grey"); + // font-size: 1rem; + font-size: 0.8rem; + } + + td.completed span { + background: vars.getColor('green'); + color: vars.darkenColor('green'); + border-radius: 4px; + padding: 2px; + } + + td.processing { + color: vars.getColor('light-blue'); + } + + td.canceled { + color: vars.getColor('red-alert'); + } + + .bi-eye-fill { + color: grey; + } + + .nav-btn.right { + margin-right: 20px; + } + + .nav-btn.left { + margin-left: 20px; + } +} + +.pending-pane { + .pending-heading { + position: relative; + border-bottom: 1px solid vars.getColor('light2-grey'); + // background-color: vars.getColor('light-grey'); + &:hover { + color: vars.darkenColor('text-blue-medium'); + } + } + + .pending-content { + color: grey; + height: 0; + overflow: hidden; + transition: all 0.2s; + } + + .selected .pending-content { + height: fit-content; + } + + li { + margin: 10px 0; + display: inline-block; + width: 15em; + } + + img { + // border: 2px solid vars.getColor('light2-grey'); + padding: 4px; + // border-radius: 50%; + margin-top: 5px; + width: 25px; + display: inline-block; + margin-bottom: 3px; + margin-left: 2px; + position: absolute; + transition: all 0.2s; + } + + .selected img { + transform: rotate(180deg); + } +} + +.history-pane { + transition: all 0.2s; + .nav-btn { + width: 1.5em; + // transform: translateY(1.5em); + + } + .nav-legend { + color: grey; + // transform: translateX(-1.5em); + text-align: center; + // position: absolute; + width: 3em; + left: 50%; + } + + table { + min-width: 30em; + } + + .table-scroller { + width: 100%; + overflow-x: scroll; + } + +} + +.actions { + margin-bottom: 1em; +} +.actions a { + color: vars.getColor('brand-orange'); + margin: 1em; + margin-bottom: 2em; + padding: 3px; +} + +.actions a:hover { + color: vars.darkenColor('brand-orange'); +} + +.logout-pane { + margin-top: 25%; + text-align: center; + height: 10em; + a { + display: block; + // vertical-align: center; + margin: auto; + margin-top: 2em; + height: 1.3em; + width: 5em; + @include vars.inverting-button(black, white); + } +} + +.settings-page { + h3 { + text-align: center; + } + h5 { + display: inline-block; + margin-right: 2em; + margin-top: 0; + margin-bottom: 0.5em; + color: vars.getColor('brand-orange'); + } + input { + display: block; + margin-bottom: 1em; + right: 2px; + position: relative; + } + + button { + &>*{vertical-align: middle;} + margin-right: 10px; + display: inline-block; + @include vars.inverting-button(vars.getColor('text-blue-medium'), white); + } + + .loading-icon { + width: 1.5em; + height: 1.5em; + display: none; + color: white; + margin-left: 0.5em; + margin-right: 0.5em; + } + + .loading .loading-icon { + display: inline; + } + + .completed span { + color: vars.getColor('green'); + } + + .error span { + color: vars.getColor('red-alert'); + } +} + +.services-legend { + display: flex; + justify-content: space-between; + margin-top: 20px; + border-top: 1px solid darkgrey; + border-bottom: 1px solid darkgrey; + min-height: 2em; + background-color: rgb(239, 239, 239); + color: rgb(118, 118, 118); + padding-right: 30px; + h5 { + display: inline-block; + margin: auto; + text-align: center; + width: 20%; + // max-width: 25%; + } + :nth-child(1) { + width: 30%; + } +} + +.sliding-menu { + display: flex; + justify-content: center; + flex-basis: 50%; + margin-top: 1em; + flex-flow: wrap; + gap: 10px; + a { + width: 25%; + font-size: 1.1rem; + background: none; + border: none; + color: grey; + text-align: center; + } + .selected { + color: black; + } +} + +.menu-slider { + width: 55%; + div { + transition: all 0.2s; + width: 50%; + border-bottom: 2px solid black; + flex: 2; + } +} + +.menu-slider.credits div { + transform: translateX(100%); +} + +.menu-slider.right div { + transform: translateX(100%); +} + +.services-pane { + li { + display: flex; + // gap: 0.5em; + justify-content: space-between; + align-items: center; + border-top: 1px solid grey; + // border-bottom: 1px solid grey; + padding: 6px 0; + } + + li span { + // width: 10%; + text-align:center; + vertical-align: middle; + } + + li :nth-child(1) { + width: 30%; + } + + li:hover span { + color: vars.darkenColor('faded-text'); + } + + svg { + width: 25px; + height: 25px; + color: vars.getColor('brand-orange'); + right: 10px; + } + + svg:hover { + color: vars.getColor('dark-orange'); + } +} + + +#overlay { + position: fixed; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.5); + left: 0; + top: 0; + + .overlay-item { + width: 20em; + min-height: 20em; + background: white; + margin: auto; + margin-top: 20%; + opacity: 1; + border-radius: 4px; + padding: 5px; + @include vars.hovering2; + + div { + width: 15em; + margin: auto; + margin-bottom: 1em; + } + + input { + // width: 40%; + display: inline; + } + } + + .icon { + width: 40px; + height: 40px; + display: block; + margin: 0 auto; + } + + h3 { + text-align: center; + margin-top: 5px; + } + + h4 { + text-align: center; + } + + .cancel { + position: absolute; + width: 30px; + right: 10px; + top: 10px; + } + + button { + display: inline-block; + margin: auto; + display: flex; + align-items: center; + text-align: center; + justify-content: center; + @include vars.inverting-button(vars.getColor('brand-orange'), white); + font-size: 1.1rem; + min-width: 7em; + } + + #url { + width: 100%; + } + + p { + margin-top: 2em; + text-align: center; + } + + li { + margin: 1em 0; + } + +} + +button .loading-icon { + display: inline; + height: 1.5em; +} + +#panel #main .credits-display { + text-align: center; + margin: 1em; + color: vars.getColor('brand-orange'); + display: flex; + justify-content: center; + align-items: center; +} + +.select-credits .credits-pane { + flex-flow: wrap; + border-radius: 4px; + margin-bottom: 2em; + text-align: center; + display: flex; + gap: 5%; + padding: 0.5em 0; + + span {margin-right: 0.5em;} +} + +.select-credits h2 { + margin: 0; +} + +.select-credits h3 { + text-align: center; +} + +.select-credits input { + width: 3em; +} + +.brand-btn { + display: inline-block; + margin: auto; + display: flex; + align-items: center; + text-align: center; + justify-content: center; + @include vars.inverting-button(vars.getColor('brand-orange'), white); + font-size: 1.1rem; + min-width: 7em; +} + +div#card-errors { + margin: 5%; + text-align: center; +} + +#payment-section { + h4 { + text-align: center; + } + + form { + margin-top: 2em; + } + + label { + display: block; + } + + input { + margin: 1em 0; + } + + #save-card { + display: flex; + gap: 10px; + align-items: center; + justify-content: center; + } + + #billing-name { + width: 25em; + max-width: 90%; + } +} + +#card-element { + border: 2px solid grey; + border-radius: 4px; + padding: 10px; +} + +.info-grey { + text-align: center; + color: grey; + // color: vars.getColor('light2-grey'); + div { + width: 20%; + margin: auto; + height: 4px; + margin-top: 1em; + background: vars.getColor('light2-grey'); + border-radius: 2px; + } +} + +.saved-card { + display: flex; + gap: 15px; + align-items: center; + justify-content: space-around; + max-width: 15em; + margin: auto; +} + +.status-dialog { + position: relative; + top: 15%; + margin: auto; + max-width: 20em; + text-align: center; + + img { + margin: auto; + display: block; + } +} + +.saved-cards-heading { + display: flex; + align-items: center; + justify-content: space-around; + max-width: 25em; + margin: auto; + + h5 { + margin: 0; + } + +} + +.settings-page .saved-card { + max-width: 25em; + // align-items: start; + justify-content: space-between; + margin-top: 1em; + + > * { + min-width: 20%; + } + + img { + width: 15px; + display: block; + } + + input { + display: block; + margin: auto; + } +} + +.saved-card button { + width: 3em; + height: 2em; +} + +nav.info-page { + position: relative; +} + +main.terms { + section { + padding-top: 0; + padding-bottom: 2em; + padding-right: 5%; + padding-left: 5%; + max-width: 50em; + margin: auto; + } + + section:nth-child(1) { + margin-top: 3em; + } + + p { + color: grey; + } +} + +.payment-window { + min-height: 5em; + margin-top: 1em; + text-align: center; + color: grey; + img { + width: 250px; + max-width: 80%; + margin: auto; + margin-bottom: 1em; + display: block; + height: 90px; + } +} + +#agreement-check { + margin: auto; + max-width: 30em; + display: flex; + align-items: center; + + label { + display: inline-block; + // width: 85%; + margin: 1em; + } + + input { + display: inline-block; + } +} + +.support-section .loading-icon { + margin-top: 25%; + color: vars.getColor('brand-orange'); +} + +#support-form { + margin-top: 10%; + + label { + margin: auto; + text-align: center; + width: 10em; + display: block; + margin-bottom: 1em; + } + + select { + display: block; + margin: auto; + margin-bottom: 2em; + width: 10em; + text-align: center; + background: white; + } + + textarea { + display: block; + width: 90%; + margin: auto; + } + + button { + display: block; + font-size: 1em; + margin: auto; + margin-top: 2em; + height: 2.3em; + width: 5em; + @include vars.inverting-button(black, white); + } +} + +.error-message { + text-align: center; + color: vars.getColor('red-alert'); + margin-top: 1em; + margin-bottom: 1em; +} + +//Maybe this should have a max width +.dialog { + text-align: center; +} + diff --git a/php-laravel/views/app.blade.php b/php-laravel/views/app.blade.php new file mode 100644 index 0000000..9184f89 --- /dev/null +++ b/php-laravel/views/app.blade.php @@ -0,0 +1 @@ +Vue App
\ No newline at end of file diff --git a/php-laravel/views/change-email.blade.php b/php-laravel/views/change-email.blade.php new file mode 100644 index 0000000..979295f --- /dev/null +++ b/php-laravel/views/change-email.blade.php @@ -0,0 +1,12 @@ +
+

An email change has been requested from {{$current_email}}

+ +
+

Confirm

+
+ +
+ If you do not recognize this activity, please disregard this + message. +
+
diff --git a/php-laravel/views/email-changed.blade.php b/php-laravel/views/email-changed.blade.php new file mode 100644 index 0000000..8dd5efd --- /dev/null +++ b/php-laravel/views/email-changed.blade.php @@ -0,0 +1,25 @@ +@extends('master') + +@section('title', 'Settings') +@section('heading-style', 'info-page') + +@section('head-metas') + @parent + +@endsection + +@section('content') +
+
+

Your email has been reset.

+ +
+
+@endsection + +@section('scripts') + @parent + + + +@endsection diff --git a/php-laravel/views/home.blade.php b/php-laravel/views/home.blade.php new file mode 100644 index 0000000..2be5872 --- /dev/null +++ b/php-laravel/views/home.blade.php @@ -0,0 +1,228 @@ +@extends('master') + +@section('title', 'Home') +@section('heading-style', 'Home') + +@section('head-metas') + @parent +@endsection + +@section('login-form') + @guest +
+
+ + +
+
+ + +
+ +

+
+