diff --git a/.env.dev b/.env.dev new file mode 100644 index 00000000..92afc86a --- /dev/null +++ b/.env.dev @@ -0,0 +1,67 @@ +APP_NAME=VueFileManager +APP_ENV=local +APP_KEY=base64:EYM98pyseC/frZhW30ifeJqpOP3UmmLj1fMahrDN3zw= +APP_DEBUG=true +APP_URL=http://localhost +APP_DEMO=false + +LOG_CHANNEL=stack +SCOUT_DRIVER=tntsearch +FILESYSTEM_DRIVER=local + +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=8889 +DB_DATABASE=file-manager +DB_USERNAME=root +DB_PASSWORD=root + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +QUEUE_CONNECTION=sync +SESSION_DRIVER=file +SESSION_LIFETIME=120 + +MAIL_DRIVER=smtp +MAIL_HOST=smtp.websupport.sk +MAIL_PORT=25 +MAIL_USERNAME=vuefilemanager@hi5ve.digital +MAIL_PASSWORD=Yl2d]kET>) +MAIL_ENCRYPTION=tls +MAIL_FROM_ADDRESS="${MAIL_USERNAME}" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID=AKIA3UOZZXPUGKQ3BLMZ +AWS_SECRET_ACCESS_KEY=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 +AWS_DEFAULT_REGION=eu-central-1 +AWS_BUCKET=vuefilemanager-frankfurt + +DO_SPACES_KEY=FSUHFOF5DMZCEWXVNPTI +DO_SPACES_SECRET=2t87Ugz/JoIjT1dIdTjnnKQ4t74Yfe665KBjCRc0yDk +DO_SPACES_ENDPOINT=https://fra1.digitaloceanspaces.com +DO_SPACES_REGION=fra1 +DO_SPACES_BUCKET=vuefilemanager + +WASABI_KEY= +WASABI_SECRET= +WASABI_ENDPOINT= +WASABI_REGION= +WASABI_BUCKET= + +BACKBLAZE_KEY= +BACKBLAZE_SECRET= +BACKBLAZE_ENDPOINT= +BACKBLAZE_REGION= +BACKBLAZE_BUCKET= + +PASSPORT_CLIENT_ID=1 +PASSPORT_CLIENT_SECRET=kZevoWiMjuoxlfO0N0Ezq2oo6ukvX27VPEEJlQUD + +APP_DEPLOY_SECRET=5603148y60eew0q5fw46 +APP_DEPLOY_BRANCH=dev + +CASHIER_LOGGER=stack +CASHIER_CURRENCY=usd +STRIPE_KEY=pk_test_51GsACaCBETHMUxzVsYkeApHtqb85paMuye7G77PDDQ28kXqDJ5HTmqLi13aM6xee81OQK1fhkTZ7vmDiWLStU9160061Yb2MtL +STRIPE_SECRET=sk_test_51GsACaCBETHMUxzVviYCrv0CeZMyWAOfBPe4uH5rkKJcJxrXhIciWQTr7UB1sgw9geoJMkNDVSWBQW36tuAsVznd00zhNHXhok +STRIPE_WEBHOOK_SECRET=whsec_5aM5emy4U9AzPLFxOPyBSyI0QGyI1MZW diff --git a/.env.example b/.env.example index e69eb806..bed60c01 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ APP_NAME=Laravel APP_ENV=local -APP_KEY= +APP_KEY=base64:sB1YuKsbWv7MdWugb9ZsYBqv2QZJ+QOuHZHEddOsUuo= APP_DEBUG=true APP_URL=http://localhost @@ -45,10 +45,25 @@ DO_SPACES_ENDPOINT= DO_SPACES_REGION= DO_SPACES_BUCKET= -MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" -MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" +WASABI_KEY= +WASABI_SECRET= +WASABI_ENDPOINT= +WASABI_REGION= +WASABI_BUCKET= + +BACKBLAZE_KEY= +BACKBLAZE_SECRET= +BACKBLAZE_ENDPOINT= +BACKBLAZE_REGION= +BACKBLAZE_BUCKET= PASSPORT_CLIENT_ID= PASSPORT_CLIENT_SECRET= -APP_DEPLOY_SECRET= \ No newline at end of file +APP_DEPLOY_SECRET= + +CASHIER_LOGGER=stack +CASHIER_CURRENCY= +STRIPE_KEY= +STRIPE_SECRET= +STRIPE_WEBHOOK_SECRET= \ No newline at end of file diff --git a/app/Console/Commands/SetupProductionEnvironment.php b/app/Console/Commands/SetupProductionEnvironment.php index 19b767fd..ea9c9a67 100644 --- a/app/Console/Commands/SetupProductionEnvironment.php +++ b/app/Console/Commands/SetupProductionEnvironment.php @@ -63,9 +63,6 @@ class SetupProductionEnvironment extends Command public function migrateDatabase() { $this->call('migrate:fresh'); - $this->call('db:seed', [ - '--class' => 'PaymentGatewaysSeeder' - ]); } /** diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index b642dbb8..9d861fa8 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -171,8 +171,6 @@ class UserController extends Controller { // Store avatar if ($request->hasFile('avatar')) { - - // Update avatar $avatar = store_avatar($request->file('avatar'), 'avatars'); } diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index 8601688a..744fa22e 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -20,20 +20,22 @@ class AuthController extends Controller * @param Request $request * @return mixed */ - public function check_account(CheckAccountRequest $request) { + public function check_account(CheckAccountRequest $request) + { // Get User $user = User::where('email', $request->input('email'))->select(['name', 'avatar'])->first(); // Return user info if ($user) return [ - 'name' => $user->name, + 'name' => $user->name, 'avatar' => $user->avatar, ]; // Abort with 404, user not found return abort('404', __('vuefilemanager.user_not_fount')); } + /** * Login user * @@ -42,17 +44,16 @@ class AuthController extends Controller */ public function login(Request $request) { - $response = Route::dispatch(self::make_request($request)); + $response = Route::dispatch(self::make_login_request($request)); if ($response->isSuccessful()) { $data = json_decode($response->content(), true); return response('Login Successfull!', 200)->cookie('access_token', $data['access_token'], 43200); - } else { - - return $response; } + + return $response; } /** @@ -64,11 +65,11 @@ class AuthController extends Controller public function register(Request $request) { // Check if account registration is enabled - if (! config('vuefilemanager.registration') ) abort(401); + if (!config('vuefilemanager.registration')) abort(401); // Validate request $request->validate([ - 'name' => ['required', 'string', 'max:255'], + 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:6', 'confirmed'], ]); @@ -81,21 +82,22 @@ class AuthController extends Controller ]); // Create settings + // TODO: set default storage capacity $settings = UserSettings::create([ - 'user_id' => $user->id + 'user_id' => $user->id, + 'storage_capacity' => 5, ]); - $response = Route::dispatch(self::make_request($request)); + $response = Route::dispatch(self::make_login_request($request)); if ($response->isSuccessful()) { $data = json_decode($response->content(), true); return response('Register Successfull!', 200)->cookie('access_token', $data['access_token'], 43200); - } else { - - return $response; } + + return $response; } /** @@ -106,7 +108,7 @@ class AuthController extends Controller public function logout() { // Demo preview - if (is_demo( Auth::id())) { + if (is_demo(Auth::id())) { return response('Logout successfull', 204) ->cookie('access_token', '', -1); } @@ -118,18 +120,17 @@ class AuthController extends Controller $token->delete(); }); - return response('Logout successfull', 204) + return response('Logout successful', 204) ->cookie('access_token', '', -1); } /** - * Make request for get user token + * Make login request for get access token * * @param Request $request - * @param string $provider * @return Request */ - private static function make_request($request) + private static function make_login_request($request) { $request->request->add([ 'grant_type' => 'password', diff --git a/app/Http/Controllers/General/SetupWizardController.php b/app/Http/Controllers/General/SetupWizardController.php new file mode 100644 index 00000000..93819a8a --- /dev/null +++ b/app/Http/Controllers/General/SetupWizardController.php @@ -0,0 +1,607 @@ +stripe = $stripe; + } + + /** + * Verify Envato purchase code + * + * @param Request $request + * @return ResponseFactory|\Illuminate\Http\Response|mixed + */ + public function verify_purchase_code(Request $request) + { + // Author API token + $token = 'X3kPkRnIHqauwE7vle3Gvhx6PTY9bvLr'; + // e3420e63-ce6f-4d04-9b3e-f7f5cc6af7c6 + + // Verify purchase code + $response = Http::withHeaders([ + 'Authorization' => 'Bearer ' . $token + ])->get('https://api.envato.com/v3/market/author/sale?code=' . $request->purchaseCode); + + if ($response->successful()) { + return $response['license']; + } + + return response('Purchase code is invalid.', 400); + } + + /** + * Set up database credentials + * + * @param StoreDatabaseCredentialsRequest $request + * @return ResponseFactory|\Illuminate\Http\Response + */ + public function setup_database(StoreDatabaseCredentialsRequest $request) + { + // Set temporary database connection + config(['database.connections.test.driver' => $request->connection]); + config(['database.connections.test.host' => $request->host]); + config(['database.connections.test.port' => $request->port]); + config(['database.connections.test.database' => $request->name]); + config(['database.connections.test.username' => $request->username]); + config(['database.connections.test.password' => $request->password]); + + // Test database connection + try { + \DB::connection('test')->getPdo(); + + } catch (PDOException $e) { + throw new HttpException(500, $e->getMessage()); + } + + $database_credentials = collect([ + [ + 'name' => 'DB_CONNECTION', + 'value' => $request->connection, + ], + [ + 'name' => 'DB_HOST', + 'value' => $request->host, + ], + [ + 'name' => 'DB_PORT', + 'value' => $request->port, + ], + [ + 'name' => 'DB_DATABASE', + 'value' => $request->name, + ], + [ + 'name' => 'DB_USERNAME', + 'value' => $request->username, + ], + [ + 'name' => 'DB_PASSWORD', + 'value' => $request->password, + ], + ]); + + // Store database credentials + $database_credentials->each(function ($col) { + $this->setEnvironmentValue($col['name'], $col['value']); + }); + + // Set up application + $this->set_up_application(); + + return response('Done', 200); + } + + /** + * Migrate database and generate necessary things + */ + private function set_up_application() + { + // Clear Cache + Artisan::call('cache:clear'); + Artisan::call('config:clear'); + + // Generate app key + Artisan::call('key:generate'); + + // Migrate database + Artisan::call('migrate:fresh'); + + // Create Passport Keys + Artisan::call('passport:keys', [ + '--force' => true + ]); + + // Create Password grant client + Artisan::call('passport:client', [ + '--password' => true, + '--name' => 'vuefilemanager', + ]); + + // Create Personal access client + Artisan::call('passport:client', [ + '--personal' => true, + '--name' => 'shared', + ]); + + // Get generated client + $client = \DB::table('oauth_clients')->where('name', '=', 'vuefilemanager')->first(); + + // Set passport client to .env + $this->setEnvironmentValue('PASSPORT_CLIENT_ID', $client->id); + $this->setEnvironmentValue('PASSPORT_CLIENT_SECRET', $client->secret); + } + + /** + * Store and test stripe credentials + * + * @param StoreStripeCredentialsRequest $request + * @return ResponseFactory|\Illuminate\Http\Response + */ + public function store_stripe_credentials(StoreStripeCredentialsRequest $request) + { + // Create stripe instance + $stripe = Stripe::make($request->secret, '2020-03-02'); + + // Try to get stripe account details + try { + $stripe->account()->details(); + } catch (UnauthorizedException $e) { + throw new HttpException(401, $e->getMessage()); + } + + // Get options + $settings = collect([ + [ + 'name' => 'stripe_currency', + 'value' => $request->currency, + ], + [ + 'name' => 'stripe_webhook_secret', + 'value' => $request->webhookSecret, + ], + [ + 'name' => 'stripe_secret_key', + 'value' => $request->secret, + ], + [ + 'name' => 'stripe_publishable_key', + 'value' => $request->key, + ], + ]); + + // Set stripe credentials to .env + $this->setEnvironmentValue('CASHIER_CURRENCY', $request->currency); + $this->setEnvironmentValue('STRIPE_KEY', $request->key); + $this->setEnvironmentValue('STRIPE_SECRET', $request->secret); + $this->setEnvironmentValue('STRIPE_WEBHOOK_SECRET', $request->webhookSecret); + + // Store options + $settings->each(function ($col) { + Setting::updateOrCreate(['name' => $col['name']], $col); + }); + + return response('Done', 200); + } + + /** + * Store Stripe billings + * + * @param StoreStripeBillingRequest $request + * @return ResponseFactory|\Illuminate\Http\Response + */ + public function store_stripe_billings(StoreStripeBillingRequest $request) + { + // Get options + $settings = collect([ + [ + 'name' => 'billing_phone_number', + 'value' => $request->billing_phone_number, + ], + [ + 'name' => 'billing_postal_code', + 'value' => $request->billing_postal_code, + ], + [ + 'name' => 'billing_vat_number', + 'value' => $request->billing_vat_number, + ], + [ + 'name' => 'billing_address', + 'value' => $request->billing_address, + ], + [ + 'name' => 'billing_country', + 'value' => $request->billing_country, + ], + [ + 'name' => 'billing_state', + 'value' => $request->billing_state, + ], + [ + 'name' => 'billing_city', + 'value' => $request->billing_city, + ], + [ + 'name' => 'billing_name', + 'value' => $request->billing_name, + ], + ]); + + // Store options + $settings->each(function ($col) { + Setting::updateOrCreate(['name' => $col['name']], $col); + }); + + return response('Done', 200); + } + + /** + * Create Stripe subscription plan + * + * @param StoreStripePlansRequest $request + */ + public function store_stripe_plans(StoreStripePlansRequest $request) + { + foreach ($request->input('plans') as $plan) { + $this->stripe->createPlan($plan); + } + } + + /** + * Store environment setup + * + * @param StoreEnvironmentSetupRequest $request + * @return string + */ + public function store_environment_setup(StoreEnvironmentSetupRequest $request) + { + $storage_driver = $request->input('storage.driver'); + + if ($storage_driver === 'local') { + + $storage = collect([ + [ + 'name' => 'FILESYSTEM_DRIVER', + 'value' => 'local', + ], + ]); + + } else if ($storage_driver === 's3') { + + $storage = collect([ + [ + 'name' => 'FILESYSTEM_DRIVER', + 'value' => $request->input('storage.driver'), + ], + [ + 'name' => 'AWS_ACCESS_KEY_ID', + 'value' => $request->input('storage.key'), + ], + [ + 'name' => 'AWS_SECRET_ACCESS_KEY', + 'value' => $request->input('storage.secret'), + ], + [ + 'name' => 'AWS_DEFAULT_REGION', + 'value' => $request->input('storage.region'), + ], + [ + 'name' => 'AWS_BUCKET', + 'value' => $request->input('storage.bucket'), + ], + ]); + + } else if ($storage_driver === 'spaces') { + + $storage = collect([ + [ + 'name' => 'FILESYSTEM_DRIVER', + 'value' => $request->input('storage.driver'), + ], + [ + 'name' => 'DO_SPACES_KEY', + 'value' => $request->input('storage.key'), + ], + [ + 'name' => 'DO_SPACES_SECRET', + 'value' => $request->input('storage.secret'), + ], + [ + 'name' => 'DO_SPACES_ENDPOINT', + 'value' => $request->input('storage.endpoint'), + ], + [ + 'name' => 'DO_SPACES_REGION', + 'value' => $request->input('storage.region'), + ], + [ + 'name' => 'DO_SPACES_BUCKET', + 'value' => $request->input('storage.bucket'), + ], + ]); + + } else if ($storage_driver === 'wasabi') { + + $storage = collect([ + [ + 'name' => 'FILESYSTEM_DRIVER', + 'value' => $request->input('storage.driver'), + ], + [ + 'name' => 'WASABI_KEY', + 'value' => $request->input('storage.key'), + ], + [ + 'name' => 'WASABI_SECRET', + 'value' => $request->input('storage.secret'), + ], + [ + 'name' => 'WASABI_ENDPOINT', + 'value' => $request->input('storage.endpoint'), + ], + [ + 'name' => 'WASABI_REGION', + 'value' => $request->input('storage.region'), + ], + [ + 'name' => 'WASABI_BUCKET', + 'value' => $request->input('storage.bucket'), + ], + ]); + + } else if ($storage_driver === 'backblaze') { + + $storage = collect([ + [ + 'name' => 'FILESYSTEM_DRIVER', + 'value' => $request->input('storage.driver'), + ], + [ + 'name' => 'BACKBLAZE_KEY', + 'value' => $request->input('storage.key'), + ], + [ + 'name' => 'BACKBLAZE_SECRET', + 'value' => $request->input('storage.secret'), + ], + [ + 'name' => 'BACKBLAZE_ENDPOINT', + 'value' => $request->input('storage.endpoint'), + ], + [ + 'name' => 'BACKBLAZE_REGION', + 'value' => $request->input('storage.region'), + ], + [ + 'name' => 'BACKBLAZE_BUCKET', + 'value' => $request->input('storage.bucket'), + ], + ]); + } + + // Store storage driver options + $storage->each(function ($col) { + $this->setEnvironmentValue($col['name'], $col['value']); + }); + + // Get options + $mail = collect([ + [ + 'name' => 'MAIL_DRIVER', + 'value' => $request->input('mail.driver'), + ], + [ + 'name' => 'MAIL_HOST', + 'value' => $request->input('mail.host'), + ], + [ + 'name' => 'MAIL_PORT', + 'value' => $request->input('mail.port'), + ], + [ + 'name' => 'MAIL_USERNAME', + 'value' => $request->input('mail.username'), + ], + [ + 'name' => 'MAIL_PASSWORD', + 'value' => $request->input('mail.password'), + ], + [ + 'name' => 'MAIL_ENCRYPTION', + 'value' => $request->input('mail.encryption'), + ], + ]); + + // Store mail options + $mail->each(function ($col) { + $this->setEnvironmentValue($col['name'], $col['value']); + }); + + return response('Done', 200); + } + + /** + * Store app settings + * @param StoreAppSetupRequest $request + * @return ResponseFactory|\Illuminate\Http\Response + */ + public function store_app_settings(StoreAppSetupRequest $request) + { + // Store Logo + if ($request->hasFile('logo')) { + $logo = store_system_image($request->file('logo'), 'system'); + } + + // Store favicon + if ($request->hasFile('favicon')) { + $favicon = store_system_image($request->file('favicon'), 'system'); + } + + // Get options + $settings = collect([ + [ + 'name' => 'app_title', + 'value' => $request->title, + ], + [ + 'name' => 'app_description', + 'value' => $request->description, + ], + [ + 'name' => 'app_logo', + 'value' => $request->hasFile('logo') ? $logo : null, + ], + [ + 'name' => 'app_favicon', + 'value' => $request->hasFile('favicon') ? $favicon : null, + ], + [ + 'name' => 'google_analytics', + 'value' => $request->googleAnalytics, + ], + [ + 'name' => 'contact_email', + 'value' => $request->contactMail, + ], + [ + 'name' => 'registration', + 'value' => $request->userRegistration, + ], + [ + 'name' => 'storage_limitation', + 'value' => $request->storageLimitation, + ], + [ + 'name' => 'storage_default', + 'value' => $request->defaultStorage, + ], + ]); + + // Store options + $settings->each(function ($col) { + Setting::updateOrCreate(['name' => $col['name']], $col); + }); + + return response('Done', 200); + } + + /** + * Create and login admin account + * + * @param Request $request + * @return ResponseFactory|\Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response + */ + public function create_admin_account(Request $request) + { + // Validate request + $request->validate([ + 'email' => 'required|string|email|unique:users', + 'password' => 'required|string|min:6|confirmed', + 'name' => 'required|string', + 'avatar' => 'sometimes|file', + ]); + + // Store avatar + if ($request->hasFile('avatar')) { + $avatar = store_avatar($request->file('avatar'), 'avatars'); + } + + // Create user + $user = User::create([ + 'avatar' => $request->hasFile('avatar') ? $avatar : null, + 'name' => $request->name, + 'role' => 'admin', + 'email' => $request->email, + 'password' => Hash::make($request->password), + ]); + + // Create settings + // TODO: set default storage capacity + UserSettings::create([ + 'user_id' => $user->id, + 'storage_capacity' => 1, + ]); + + // Retrieve access token + $response = Route::dispatch(self::make_login_request($request)); + + // Send access token to user if request is successful + if ($response->isSuccessful()) { + + $data = json_decode($response->content(), true); + + return response('Admin was created', 200)->cookie('access_token', $data['access_token'], 43200); + } + + return $response; + } + + /** + * Make login request for get access token + * + * @param Request $request + * @return Request + */ + private static function make_login_request($request) + { + $request->request->add([ + 'grant_type' => 'password', + 'client_id' => config('services.passport.client_id'), + 'client_secret' => config('services.passport.client_secret'), + 'username' => $request->email, + 'password' => $request->password, + 'scope' => 'master', + ]); + + return Request::create(url('/oauth/token'), 'POST', $request->all()); + } + + /** + * Set environment value + * + * @param $key + * @param $value + */ + public function setEnvironmentValue($key, $value) + { + $env_path = app()->environmentFilePath(); + + $escaped = preg_quote('=' . env($key), '/'); + + file_put_contents($env_path, preg_replace( + "/^{$key}{$escaped}/m", + $key . '=' . $value, + file_get_contents($env_path) + )); + } +} diff --git a/app/Http/Helpers/helpers.php b/app/Http/Helpers/helpers.php index e75114c9..e29eb118 100644 --- a/app/Http/Helpers/helpers.php +++ b/app/Http/Helpers/helpers.php @@ -179,6 +179,28 @@ function store_avatar($image, $path) return $path . '/' . $image_path; } +/** + * Store system image + * + * @param $image + * @param $path + * @return string + */ +function store_system_image($image, $path) +{ + // Get directory + $path = check_directory($path); + + // Store avatar + $image_path = Str::random(8) . '-' . str_replace(' ', '', $image->getClientOriginalName()); + + // Store image to disk + Storage::putFileAs($path, $image, $image_path); + + // Return path to image + return $path . '/' . $image_path; +} + /** * Check if directory exist, if no, then create it * diff --git a/app/Http/Requests/SetupWizard/StoreAppSetupRequest.php b/app/Http/Requests/SetupWizard/StoreAppSetupRequest.php new file mode 100644 index 00000000..f298a5ef --- /dev/null +++ b/app/Http/Requests/SetupWizard/StoreAppSetupRequest.php @@ -0,0 +1,38 @@ + 'required|string', + 'description' => 'required|string', + 'logo' => 'sometimes|file', + 'favicon' => 'sometimes|file', + 'contactMail' => 'required|email', + 'googleAnalytics' => 'required|string', + 'defaultStorage' => 'required|digits_between:1,9', + 'userRegistration' => 'required|boolean', + 'storageLimitation' => 'required|boolean', + ]; + } +} diff --git a/app/Http/Requests/SetupWizard/StoreDatabaseCredentialsRequest.php b/app/Http/Requests/SetupWizard/StoreDatabaseCredentialsRequest.php new file mode 100644 index 00000000..c81fd16e --- /dev/null +++ b/app/Http/Requests/SetupWizard/StoreDatabaseCredentialsRequest.php @@ -0,0 +1,35 @@ + 'required|string', + 'host' => 'required|string', + 'port' => 'required|string', + 'name' => 'required|string', + 'username' => 'required|string', + 'password' => 'required|string', + ]; + } +} diff --git a/app/Http/Requests/SetupWizard/StoreEnvironmentSetupRequest.php b/app/Http/Requests/SetupWizard/StoreEnvironmentSetupRequest.php new file mode 100644 index 00000000..5834d31d --- /dev/null +++ b/app/Http/Requests/SetupWizard/StoreEnvironmentSetupRequest.php @@ -0,0 +1,43 @@ + 'required|array', + 'storage.driver' => 'required|string', + 'storage.key' => 'sometimes|nullable|string', + 'storage.secret' => 'sometimes|nullable|string', + 'storage.endpoint' => 'sometimes|nullable|string', + 'storage.region' => 'sometimes|nullable|string', + 'storage.bucket' => 'sometimes|nullable|string', + 'mail' => 'required|array', + 'mail.driver' => 'required|string', + 'mail.host' => 'required|string', + 'mail.port' => 'required|string', + 'mail.username' => 'required|string', + 'mail.password' => 'required|string', + 'mail.encryption' => 'required|string', + ]; + } +} diff --git a/app/Http/Requests/SetupWizard/StoreStripeBillingRequest.php b/app/Http/Requests/SetupWizard/StoreStripeBillingRequest.php new file mode 100644 index 00000000..3b825a9f --- /dev/null +++ b/app/Http/Requests/SetupWizard/StoreStripeBillingRequest.php @@ -0,0 +1,37 @@ + 'sometimes|nullable|string', + 'billing_postal_code' => 'required|string', + 'billing_vat_number' => 'required|string', + 'billing_address' => 'required|string', + 'billing_country' => 'required|string', + 'billing_state' => 'required|string', + 'billing_city' => 'required|string', + 'billing_name' => 'required|string', + ]; + } +} diff --git a/app/Http/Requests/SetupWizard/StoreStripeCredentialsRequest.php b/app/Http/Requests/SetupWizard/StoreStripeCredentialsRequest.php new file mode 100644 index 00000000..e6f81a88 --- /dev/null +++ b/app/Http/Requests/SetupWizard/StoreStripeCredentialsRequest.php @@ -0,0 +1,33 @@ + 'required|string', + 'webhookSecret' => 'required|string', + 'secret' => 'required|string', + 'key' => 'required|string', + ]; + } +} diff --git a/app/Http/Requests/SetupWizard/StoreStripePlansRequest.php b/app/Http/Requests/SetupWizard/StoreStripePlansRequest.php new file mode 100644 index 00000000..5543fb35 --- /dev/null +++ b/app/Http/Requests/SetupWizard/StoreStripePlansRequest.php @@ -0,0 +1,35 @@ + 'required|array', + 'plans.*.type' => 'required|string', + 'plans.*.attributes.name' => 'required|string', + 'plans.*.attributes.price' => 'required|string', + 'plans.*.attributes.description' => 'sometimes|nullable|string', + 'plans.*.attributes.capacity' => 'required|digits_between:1,9', + ]; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index bf6e052e..75e6f63c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,6 +5,9 @@ namespace App\Providers; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; +use Laravel\Passport\Console\ClientCommand; +use Laravel\Passport\Console\InstallCommand; +use Laravel\Passport\Console\KeysCommand; class AppServiceProvider extends ServiceProvider { @@ -31,5 +34,12 @@ class AppServiceProvider extends ServiceProvider // Set locale for carbon dates setlocale(LC_TIME, $get_time_locale); + + // Install passport commands + $this->commands([ + InstallCommand::class, + ClientCommand::class, + KeysCommand::class, + ]); } } diff --git a/app/Services/StripeService.php b/app/Services/StripeService.php index b24b080b..d6325e23 100644 --- a/app/Services/StripeService.php +++ b/app/Services/StripeService.php @@ -4,6 +4,8 @@ namespace App\Services; use App\User; +use Artisan; +use Illuminate\Http\Request; use Illuminate\Support\Str; use Laravel\Cashier\Exceptions\IncompletePayment; use Stripe; @@ -16,9 +18,16 @@ class StripeService */ public function __construct() { + dd(config('stripe.secret')); + $this->stripe = Stripe::make(env('STRIPE_SECRET'), '2020-03-02'); } + /** + * Get Stripe account details + * + * @return mixed + */ public function getAccountDetails() { $account = $this->stripe->account()->details(); @@ -112,6 +121,8 @@ class StripeService } /** + * Update customer details + * * @param $user */ public function updateCustomerDetails($user) @@ -205,22 +216,38 @@ class StripeService /** * Create plan * - * @param $request + * @param $data * @return mixed */ - public function createPlan($request) + public function createPlan($data) { + if ($data instanceof Request) { + $plan = [ + 'name' => $data->input('attributes.name'), + 'description' => $data->input('attributes.description'), + 'price' => $data->input('attributes.price'), + 'capacity' => $data->input('attributes.capacity'), + ]; + } else { + $plan = [ + 'name' => $data['attributes']['name'], + 'description' => $data['attributes']['description'], + 'price' => $data['attributes']['price'], + 'capacity' => $data['attributes']['capacity'], + ]; + } + $product = $this->stripe->products()->create([ - 'name' => $request->input('attributes.name'), - 'description' => $request->input('attributes.description'), + 'name' => $plan['name'], + 'description' => $plan['description'], 'metadata' => [ - 'capacity' => $request->input('attributes.capacity') + 'capacity' => $plan['capacity'] ] ]); $plan = $this->stripe->plans()->create([ - 'id' => Str::slug($request->input('attributes.name')), - 'amount' => $request->input('attributes.price'), + 'id' => Str::slug($plan['name']), + 'amount' => $plan['price'], 'currency' => 'USD', 'interval' => 'month', 'product' => $product['id'], diff --git a/config/app.php b/config/app.php index 2e2f910e..8717e654 100644 --- a/config/app.php +++ b/config/app.php @@ -164,6 +164,7 @@ return [ TeamTNT\Scout\TNTSearchScoutServiceProvider::class, Intervention\Image\ImageServiceProvider::class, + Laravel\Passport\PassportServiceProvider::class, /* * Package Service Providers... diff --git a/config/filesystems.php b/config/filesystems.php index c0aa78fa..a525f7f5 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -73,6 +73,24 @@ return [ 'bucket' => env('DO_SPACES_BUCKET'), ], + 'wasabi' => [ + 'driver' => 's3', + 'key' => env('WASABI_KEY'), + 'secret' => env('WASABI_SECRET'), + 'endpoint' => env('WASABI_ENDPOINT'), + 'region' => env('WASABI_REGION'), + 'bucket' => env('WASABI_BUCKET'), + ], + + 'backblaze' => [ + 'driver' => 's3', + 'key' => env('BACKBLAZE_KEY'), + 'secret' => env('BACKBLAZE_SECRET'), + 'endpoint' => env('BACKBLAZE_ENDPOINT'), + 'region' => env('BACKBLAZE_REGION'), + 'bucket' => env('BACKBLAZE_BUCKET'), + ], + ], ]; diff --git a/database/migrations/2016_06_01_000001_create_oauth_auth_codes_table.php b/database/migrations/2016_06_01_000001_create_oauth_auth_codes_table.php new file mode 100644 index 00000000..6c47d247 --- /dev/null +++ b/database/migrations/2016_06_01_000001_create_oauth_auth_codes_table.php @@ -0,0 +1,35 @@ +string('id', 100)->primary(); + $table->unsignedBigInteger('user_id')->index(); + $table->unsignedBigInteger('client_id'); + $table->text('scopes')->nullable(); + $table->boolean('revoked'); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('oauth_auth_codes'); + } +} diff --git a/database/migrations/2016_06_01_000002_create_oauth_access_tokens_table.php b/database/migrations/2016_06_01_000002_create_oauth_access_tokens_table.php new file mode 100644 index 00000000..00f00633 --- /dev/null +++ b/database/migrations/2016_06_01_000002_create_oauth_access_tokens_table.php @@ -0,0 +1,37 @@ +string('id', 100)->primary(); + $table->unsignedBigInteger('user_id')->nullable()->index(); + $table->unsignedBigInteger('client_id'); + $table->string('name')->nullable(); + $table->text('scopes')->nullable(); + $table->boolean('revoked'); + $table->timestamps(); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('oauth_access_tokens'); + } +} diff --git a/database/migrations/2016_06_01_000003_create_oauth_refresh_tokens_table.php b/database/migrations/2016_06_01_000003_create_oauth_refresh_tokens_table.php new file mode 100644 index 00000000..858d0f6d --- /dev/null +++ b/database/migrations/2016_06_01_000003_create_oauth_refresh_tokens_table.php @@ -0,0 +1,33 @@ +string('id', 100)->primary(); + $table->string('access_token_id', 100); + $table->boolean('revoked'); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('oauth_refresh_tokens'); + } +} diff --git a/database/migrations/2016_06_01_000004_create_oauth_clients_table.php b/database/migrations/2016_06_01_000004_create_oauth_clients_table.php new file mode 100644 index 00000000..1dc541a3 --- /dev/null +++ b/database/migrations/2016_06_01_000004_create_oauth_clients_table.php @@ -0,0 +1,38 @@ +bigIncrements('id'); + $table->unsignedBigInteger('user_id')->nullable()->index(); + $table->string('name'); + $table->string('secret', 100)->nullable(); + $table->text('redirect'); + $table->boolean('personal_access_client'); + $table->boolean('password_client'); + $table->boolean('revoked'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('oauth_clients'); + } +} diff --git a/database/migrations/2016_06_01_000005_create_oauth_personal_access_clients_table.php b/database/migrations/2016_06_01_000005_create_oauth_personal_access_clients_table.php new file mode 100644 index 00000000..4b56435c --- /dev/null +++ b/database/migrations/2016_06_01_000005_create_oauth_personal_access_clients_table.php @@ -0,0 +1,32 @@ +bigIncrements('id'); + $table->unsignedBigInteger('client_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('oauth_personal_access_clients'); + } +} diff --git a/database/migrations/2019_05_03_000001_create_customer_columns.php b/database/migrations/2019_05_03_000001_create_customer_columns.php new file mode 100644 index 00000000..c7be66cc --- /dev/null +++ b/database/migrations/2019_05_03_000001_create_customer_columns.php @@ -0,0 +1,40 @@ +string('stripe_id')->nullable()->index(); + $table->string('card_brand')->nullable(); + $table->string('card_last_four', 4)->nullable(); + $table->timestamp('trial_ends_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn([ + 'stripe_id', + 'card_brand', + 'card_last_four', + 'trial_ends_at', + ]); + }); + } +} diff --git a/database/migrations/2019_05_03_000002_create_subscriptions_table.php b/database/migrations/2019_05_03_000002_create_subscriptions_table.php new file mode 100644 index 00000000..92ac046a --- /dev/null +++ b/database/migrations/2019_05_03_000002_create_subscriptions_table.php @@ -0,0 +1,41 @@ +bigIncrements('id'); + $table->unsignedBigInteger('user_id'); + $table->string('name'); + $table->string('stripe_id'); + $table->string('stripe_status'); + $table->string('stripe_plan')->nullable(); + $table->integer('quantity')->nullable(); + $table->timestamp('trial_ends_at')->nullable(); + $table->timestamp('ends_at')->nullable(); + $table->timestamps(); + + $table->index(['user_id', 'stripe_status']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('subscriptions'); + } +} diff --git a/database/migrations/2019_05_03_000003_create_subscription_items_table.php b/database/migrations/2019_05_03_000003_create_subscription_items_table.php new file mode 100644 index 00000000..127eff6d --- /dev/null +++ b/database/migrations/2019_05_03_000003_create_subscription_items_table.php @@ -0,0 +1,37 @@ +bigIncrements('id'); + $table->unsignedBigInteger('subscription_id'); + $table->string('stripe_id')->index(); + $table->string('stripe_plan'); + $table->integer('quantity'); + $table->timestamps(); + + $table->unique(['subscription_id', 'stripe_plan']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('subscription_items'); + } +} diff --git a/database/migrations/2020_06_02_051527_create_payment_gateways_table.php b/database/migrations/2020_06_02_051527_create_payment_gateways_table.php deleted file mode 100644 index b0b33f03..00000000 --- a/database/migrations/2020_06_02_051527_create_payment_gateways_table.php +++ /dev/null @@ -1,40 +0,0 @@ -bigIncrements('id'); - $table->boolean('status')->default(0); - $table->boolean('sandbox')->default(0); - $table->text('name'); - $table->text('slug'); - $table->text('logo'); - $table->text('client_id')->nullable(); - $table->text('secret')->nullable(); - $table->text('webhook')->nullable(); - $table->bigInteger('payment_processed')->default(0); - $table->longText('optional')->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('payment_gateways'); - } -} diff --git a/database/seeds/PaymentGatewaysSeeder.php b/database/seeds/PaymentGatewaysSeeder.php deleted file mode 100644 index c753c2c6..00000000 --- a/database/seeds/PaymentGatewaysSeeder.php +++ /dev/null @@ -1,21 +0,0 @@ -insert([ - 'logo' => '/assets/images/stripe-logo-thumbnail.png', - 'name' => 'Stripe', - 'slug' => 'stripe', - ]); - } -} diff --git a/public/assets/icons/stripe-service.svg b/public/assets/icons/stripe-service.svg new file mode 100644 index 00000000..8d281fab --- /dev/null +++ b/public/assets/icons/stripe-service.svg @@ -0,0 +1,13 @@ + + + + stripe-service + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 984aac44..dbcb58f8 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -10,5 +10,276 @@ "/js/main.ec4459f3993385a8ffb0.hot-update.js": "/js/main.ec4459f3993385a8ffb0.hot-update.js", "/js/main.4aa483cf186b82e6f133.hot-update.js": "/js/main.4aa483cf186b82e6f133.hot-update.js", "/js/main.0b62890fe58f6191cff2.hot-update.js": "/js/main.0b62890fe58f6191cff2.hot-update.js", - "/js/main.8f85a1128dbc59267b4d.hot-update.js": "/js/main.8f85a1128dbc59267b4d.hot-update.js" + "/js/main.8f85a1128dbc59267b4d.hot-update.js": "/js/main.8f85a1128dbc59267b4d.hot-update.js", + "/js/main.f57c08e4f6453554eef7.hot-update.js": "/js/main.f57c08e4f6453554eef7.hot-update.js", + "/js/main.49806237173f17d9145e.hot-update.js": "/js/main.49806237173f17d9145e.hot-update.js", + "/js/main.a66ef529551f3908fa83.hot-update.js": "/js/main.a66ef529551f3908fa83.hot-update.js", + "/js/main.d250bde2dfd0cb83ead1.hot-update.js": "/js/main.d250bde2dfd0cb83ead1.hot-update.js", + "/js/main.86c738013294c543bf9f.hot-update.js": "/js/main.86c738013294c543bf9f.hot-update.js", + "/js/main.7d5ddf17e4f5ac5d7774.hot-update.js": "/js/main.7d5ddf17e4f5ac5d7774.hot-update.js", + "/js/main.ac3441b3dd689cf2a474.hot-update.js": "/js/main.ac3441b3dd689cf2a474.hot-update.js", + "/js/main.c17831922217a8e0ca69.hot-update.js": "/js/main.c17831922217a8e0ca69.hot-update.js", + "/js/main.047d29a9f0934470628c.hot-update.js": "/js/main.047d29a9f0934470628c.hot-update.js", + "/js/main.a12c2cbb9d4f587cb915.hot-update.js": "/js/main.a12c2cbb9d4f587cb915.hot-update.js", + "/js/main.eb91f9aef7a46fc4d97d.hot-update.js": "/js/main.eb91f9aef7a46fc4d97d.hot-update.js", + "/js/main.8950f2c1f965b34c67c8.hot-update.js": "/js/main.8950f2c1f965b34c67c8.hot-update.js", + "/js/main.c181b6f235365cc77806.hot-update.js": "/js/main.c181b6f235365cc77806.hot-update.js", + "/js/main.432d249065d06227b881.hot-update.js": "/js/main.432d249065d06227b881.hot-update.js", + "/js/main.6ab44d447ea09576bf79.hot-update.js": "/js/main.6ab44d447ea09576bf79.hot-update.js", + "/js/main.f877b4f51fa54d34a703.hot-update.js": "/js/main.f877b4f51fa54d34a703.hot-update.js", + "/js/main.bfb7a5ed9a7a787bf1b1.hot-update.js": "/js/main.bfb7a5ed9a7a787bf1b1.hot-update.js", + "/js/main.7f79a324bd9f3e34330e.hot-update.js": "/js/main.7f79a324bd9f3e34330e.hot-update.js", + "/js/main.7ab0530f4fa616fd6479.hot-update.js": "/js/main.7ab0530f4fa616fd6479.hot-update.js", + "/js/main.10376c1cb6a085c800d6.hot-update.js": "/js/main.10376c1cb6a085c800d6.hot-update.js", + "/js/main.c5c07ed33eaae7e74bb0.hot-update.js": "/js/main.c5c07ed33eaae7e74bb0.hot-update.js", + "/js/main.b3898569822a4bea728a.hot-update.js": "/js/main.b3898569822a4bea728a.hot-update.js", + "/js/main.cb3e97b7379bb2690e80.hot-update.js": "/js/main.cb3e97b7379bb2690e80.hot-update.js", + "/js/main.3907d0c494c707a2e526.hot-update.js": "/js/main.3907d0c494c707a2e526.hot-update.js", + "/js/main.30c46b47c913c6750487.hot-update.js": "/js/main.30c46b47c913c6750487.hot-update.js", + "/js/main.63ac156900e0c0a6a163.hot-update.js": "/js/main.63ac156900e0c0a6a163.hot-update.js", + "/js/main.b0b834bae1baf11b7d2e.hot-update.js": "/js/main.b0b834bae1baf11b7d2e.hot-update.js", + "/js/main.ddb0da2f7522ea677587.hot-update.js": "/js/main.ddb0da2f7522ea677587.hot-update.js", + "/js/main.6b767b34769556b6f22e.hot-update.js": "/js/main.6b767b34769556b6f22e.hot-update.js", + "/js/main.388c60436fa8234d3415.hot-update.js": "/js/main.388c60436fa8234d3415.hot-update.js", + "/js/main.3c144c24f3ae3943bc90.hot-update.js": "/js/main.3c144c24f3ae3943bc90.hot-update.js", + "/js/main.bf2d7c2fb972e7d888cc.hot-update.js": "/js/main.bf2d7c2fb972e7d888cc.hot-update.js", + "/js/main.a1260c2810a8264c42e2.hot-update.js": "/js/main.a1260c2810a8264c42e2.hot-update.js", + "/js/main.d4e8446c38a1548413bb.hot-update.js": "/js/main.d4e8446c38a1548413bb.hot-update.js", + "/js/main.129032d6ff4ea189d691.hot-update.js": "/js/main.129032d6ff4ea189d691.hot-update.js", + "/js/main.bdff6b7e32d4e63268a9.hot-update.js": "/js/main.bdff6b7e32d4e63268a9.hot-update.js", + "/js/main.bb35fbbacbb8cc6a21be.hot-update.js": "/js/main.bb35fbbacbb8cc6a21be.hot-update.js", + "/js/main.f49adc3a4f0766e49fb8.hot-update.js": "/js/main.f49adc3a4f0766e49fb8.hot-update.js", + "/js/main.57e0a8a6d1694937d714.hot-update.js": "/js/main.57e0a8a6d1694937d714.hot-update.js", + "/js/main.62cbda0a1a4c8c1e7ddb.hot-update.js": "/js/main.62cbda0a1a4c8c1e7ddb.hot-update.js", + "/js/main.bfe07da70f88cbc00a18.hot-update.js": "/js/main.bfe07da70f88cbc00a18.hot-update.js", + "/js/main.1fc88be6d6fc7818d38c.hot-update.js": "/js/main.1fc88be6d6fc7818d38c.hot-update.js", + "/js/main.6575481131214e9c2891.hot-update.js": "/js/main.6575481131214e9c2891.hot-update.js", + "/js/main.c99aec40c2c3ee3eac0d.hot-update.js": "/js/main.c99aec40c2c3ee3eac0d.hot-update.js", + "/js/main.e525917a75cbb76c8b26.hot-update.js": "/js/main.e525917a75cbb76c8b26.hot-update.js", + "/js/main.22c20ab0c219852049ad.hot-update.js": "/js/main.22c20ab0c219852049ad.hot-update.js", + "/js/main.df5b1efd5e4b9c9bac80.hot-update.js": "/js/main.df5b1efd5e4b9c9bac80.hot-update.js", + "/js/main.26a5f721dbce5fad22f7.hot-update.js": "/js/main.26a5f721dbce5fad22f7.hot-update.js", + "/js/main.0d520a66a47c10f5f5e2.hot-update.js": "/js/main.0d520a66a47c10f5f5e2.hot-update.js", + "/js/main.e3d18eca82e2431524f8.hot-update.js": "/js/main.e3d18eca82e2431524f8.hot-update.js", + "/js/main.3f7d42db34a8638638f5.hot-update.js": "/js/main.3f7d42db34a8638638f5.hot-update.js", + "/js/main.12b18448bcb03190696e.hot-update.js": "/js/main.12b18448bcb03190696e.hot-update.js", + "/js/main.f1bdbd6d4e1b6125a751.hot-update.js": "/js/main.f1bdbd6d4e1b6125a751.hot-update.js", + "/js/main.7f954acdffdef3ba1b4e.hot-update.js": "/js/main.7f954acdffdef3ba1b4e.hot-update.js", + "/js/main.8af19bb7c91195200c83.hot-update.js": "/js/main.8af19bb7c91195200c83.hot-update.js", + "/js/main.83950ac988f7d52fd4db.hot-update.js": "/js/main.83950ac988f7d52fd4db.hot-update.js", + "/js/main.2668f11eaec9054d4f85.hot-update.js": "/js/main.2668f11eaec9054d4f85.hot-update.js", + "/js/main.0cb7453215e2457e963d.hot-update.js": "/js/main.0cb7453215e2457e963d.hot-update.js", + "/js/main.628d6b45839c0bd50ffa.hot-update.js": "/js/main.628d6b45839c0bd50ffa.hot-update.js", + "/js/main.494744531adf85766dd7.hot-update.js": "/js/main.494744531adf85766dd7.hot-update.js", + "/js/main.e234521251663454e5bb.hot-update.js": "/js/main.e234521251663454e5bb.hot-update.js", + "/js/main.0f8aceea8e819824f7f4.hot-update.js": "/js/main.0f8aceea8e819824f7f4.hot-update.js", + "/js/main.1c4b31ce03bd281c7af0.hot-update.js": "/js/main.1c4b31ce03bd281c7af0.hot-update.js", + "/js/main.23395410004c25e9e752.hot-update.js": "/js/main.23395410004c25e9e752.hot-update.js", + "/js/main.dec2f01b8b3413b07cce.hot-update.js": "/js/main.dec2f01b8b3413b07cce.hot-update.js", + "/js/main.90d5738c7eca52e99fa1.hot-update.js": "/js/main.90d5738c7eca52e99fa1.hot-update.js", + "/js/main.350c381ae5ef35075008.hot-update.js": "/js/main.350c381ae5ef35075008.hot-update.js", + "/js/main.7ff8cd4e0f8129b28020.hot-update.js": "/js/main.7ff8cd4e0f8129b28020.hot-update.js", + "/js/main.36981f38ef6dbe1ad2ef.hot-update.js": "/js/main.36981f38ef6dbe1ad2ef.hot-update.js", + "/js/main.c72e92249cae6c72de71.hot-update.js": "/js/main.c72e92249cae6c72de71.hot-update.js", + "/js/main.8d4d5d88816dfa6a6c3a.hot-update.js": "/js/main.8d4d5d88816dfa6a6c3a.hot-update.js", + "/js/main.6315104b66153dff7594.hot-update.js": "/js/main.6315104b66153dff7594.hot-update.js", + "/js/main.4c5a2605aac797ae5094.hot-update.js": "/js/main.4c5a2605aac797ae5094.hot-update.js", + "/js/main.06385ccc6884270b68f0.hot-update.js": "/js/main.06385ccc6884270b68f0.hot-update.js", + "/js/main.b515044cb3386d6eb38c.hot-update.js": "/js/main.b515044cb3386d6eb38c.hot-update.js", + "/js/main.daf3544f7ebd524ba8b2.hot-update.js": "/js/main.daf3544f7ebd524ba8b2.hot-update.js", + "/js/main.f49dced487020b2401db.hot-update.js": "/js/main.f49dced487020b2401db.hot-update.js", + "/js/main.61e51b120958c98e9b80.hot-update.js": "/js/main.61e51b120958c98e9b80.hot-update.js", + "/js/main.4428c57138139124d638.hot-update.js": "/js/main.4428c57138139124d638.hot-update.js", + "/js/main.2b05f7597b38aa32f3d3.hot-update.js": "/js/main.2b05f7597b38aa32f3d3.hot-update.js", + "/js/main.b6055028d561f898e936.hot-update.js": "/js/main.b6055028d561f898e936.hot-update.js", + "/js/main.78472a4d15e86cf01c4c.hot-update.js": "/js/main.78472a4d15e86cf01c4c.hot-update.js", + "/js/main.210d791d65e133956107.hot-update.js": "/js/main.210d791d65e133956107.hot-update.js", + "/js/main.ffa9b0ac8442e4d2f24b.hot-update.js": "/js/main.ffa9b0ac8442e4d2f24b.hot-update.js", + "/js/main.e2a830fd08baece2fac6.hot-update.js": "/js/main.e2a830fd08baece2fac6.hot-update.js", + "/js/main.d389a917baec2038604b.hot-update.js": "/js/main.d389a917baec2038604b.hot-update.js", + "/js/main.3e0bc14c6d62109be8c1.hot-update.js": "/js/main.3e0bc14c6d62109be8c1.hot-update.js", + "/js/main.f950652035d59e80ba88.hot-update.js": "/js/main.f950652035d59e80ba88.hot-update.js", + "/js/main.f43b68d5594c47aa9da3.hot-update.js": "/js/main.f43b68d5594c47aa9da3.hot-update.js", + "/js/main.e7caa0957bb1286588d4.hot-update.js": "/js/main.e7caa0957bb1286588d4.hot-update.js", + "/js/main.95d769d0027eed99e6c1.hot-update.js": "/js/main.95d769d0027eed99e6c1.hot-update.js", + "/js/main.77762b16026f7742cbfa.hot-update.js": "/js/main.77762b16026f7742cbfa.hot-update.js", + "/js/main.dec59254037d7f896b86.hot-update.js": "/js/main.dec59254037d7f896b86.hot-update.js", + "/js/main.a1cc927c6a50ee0c7c61.hot-update.js": "/js/main.a1cc927c6a50ee0c7c61.hot-update.js", + "/js/main.bac3134e1b5eb0e23fea.hot-update.js": "/js/main.bac3134e1b5eb0e23fea.hot-update.js", + "/js/main.d42c1993753ba7c6d7bc.hot-update.js": "/js/main.d42c1993753ba7c6d7bc.hot-update.js", + "/js/main.fe48b2784aca36a6dfbc.hot-update.js": "/js/main.fe48b2784aca36a6dfbc.hot-update.js", + "/js/main.f952b84184c741f68c44.hot-update.js": "/js/main.f952b84184c741f68c44.hot-update.js", + "/js/main.22f72f1e3bcce35c118d.hot-update.js": "/js/main.22f72f1e3bcce35c118d.hot-update.js", + "/js/main.294962aafc6753ce4207.hot-update.js": "/js/main.294962aafc6753ce4207.hot-update.js", + "/js/main.d36f9a9f6222aa74bc38.hot-update.js": "/js/main.d36f9a9f6222aa74bc38.hot-update.js", + "/js/main.0118b88fb836f75f907c.hot-update.js": "/js/main.0118b88fb836f75f907c.hot-update.js", + "/js/main.a615663568a78691f42f.hot-update.js": "/js/main.a615663568a78691f42f.hot-update.js", + "/js/main.8881ab5062a0b552e57a.hot-update.js": "/js/main.8881ab5062a0b552e57a.hot-update.js", + "/js/main.e49de8f4cfa70d1c4935.hot-update.js": "/js/main.e49de8f4cfa70d1c4935.hot-update.js", + "/js/main.3dfcd8d63417db8974ce.hot-update.js": "/js/main.3dfcd8d63417db8974ce.hot-update.js", + "/js/main.a9ce2ef2a19666a6479c.hot-update.js": "/js/main.a9ce2ef2a19666a6479c.hot-update.js", + "/js/main.2c12c92e2c72d1e41fd0.hot-update.js": "/js/main.2c12c92e2c72d1e41fd0.hot-update.js", + "/js/main.49f0ffdafe50bb99c8d4.hot-update.js": "/js/main.49f0ffdafe50bb99c8d4.hot-update.js", + "/js/main.fa9016e6dd018e588900.hot-update.js": "/js/main.fa9016e6dd018e588900.hot-update.js", + "/js/main.2b915d9e98818d0176a6.hot-update.js": "/js/main.2b915d9e98818d0176a6.hot-update.js", + "/js/main.e87a7bb1c041232220b3.hot-update.js": "/js/main.e87a7bb1c041232220b3.hot-update.js", + "/js/main.cd116661c4a1d6c5d69c.hot-update.js": "/js/main.cd116661c4a1d6c5d69c.hot-update.js", + "/js/main.582a1d128d577aec65b7.hot-update.js": "/js/main.582a1d128d577aec65b7.hot-update.js", + "/js/main.571bd2027b0ad0ac5962.hot-update.js": "/js/main.571bd2027b0ad0ac5962.hot-update.js", + "/js/main.5e91c2e331ccdfd30443.hot-update.js": "/js/main.5e91c2e331ccdfd30443.hot-update.js", + "/js/main.e7e13a6e09678c73939d.hot-update.js": "/js/main.e7e13a6e09678c73939d.hot-update.js", + "/js/main.5116d49d4cfe29bd317c.hot-update.js": "/js/main.5116d49d4cfe29bd317c.hot-update.js", + "/js/main.18894bdbb14cb9e2f4e6.hot-update.js": "/js/main.18894bdbb14cb9e2f4e6.hot-update.js", + "/js/main.4d7deb55af6c2e02cb9f.hot-update.js": "/js/main.4d7deb55af6c2e02cb9f.hot-update.js", + "/js/main.59c52d7a2f19a0cecb7c.hot-update.js": "/js/main.59c52d7a2f19a0cecb7c.hot-update.js", + "/js/main.78c184149bfbc5ca6932.hot-update.js": "/js/main.78c184149bfbc5ca6932.hot-update.js", + "/js/main.51cbbf7dc86f2a2012fa.hot-update.js": "/js/main.51cbbf7dc86f2a2012fa.hot-update.js", + "/js/main.d012d1169cb534877cd0.hot-update.js": "/js/main.d012d1169cb534877cd0.hot-update.js", + "/js/main.994c6d9f812ba73875da.hot-update.js": "/js/main.994c6d9f812ba73875da.hot-update.js", + "/js/main.fb808d6466d2dd3de777.hot-update.js": "/js/main.fb808d6466d2dd3de777.hot-update.js", + "/js/main.96d27c5eba7218a1953c.hot-update.js": "/js/main.96d27c5eba7218a1953c.hot-update.js", + "/js/main.222ebcd0c3e9d30a10a7.hot-update.js": "/js/main.222ebcd0c3e9d30a10a7.hot-update.js", + "/js/main.ec324946af056d4e9e40.hot-update.js": "/js/main.ec324946af056d4e9e40.hot-update.js", + "/js/main.cf59f620f34b75f33d57.hot-update.js": "/js/main.cf59f620f34b75f33d57.hot-update.js", + "/js/main.40983dcc9efdc97efb63.hot-update.js": "/js/main.40983dcc9efdc97efb63.hot-update.js", + "/js/main.c047afe25c97538cd057.hot-update.js": "/js/main.c047afe25c97538cd057.hot-update.js", + "/js/main.22af859fdba4f4884580.hot-update.js": "/js/main.22af859fdba4f4884580.hot-update.js", + "/js/main.2977f19ca914fd5a8b6a.hot-update.js": "/js/main.2977f19ca914fd5a8b6a.hot-update.js", + "/js/main.a5e6c89e84d4ad81c052.hot-update.js": "/js/main.a5e6c89e84d4ad81c052.hot-update.js", + "/js/main.42b8fe8b9a648085bd8e.hot-update.js": "/js/main.42b8fe8b9a648085bd8e.hot-update.js", + "/js/main.f0cfa4545280f946ba4a.hot-update.js": "/js/main.f0cfa4545280f946ba4a.hot-update.js", + "/js/main.85e3c644ebd07ea49ec3.hot-update.js": "/js/main.85e3c644ebd07ea49ec3.hot-update.js", + "/js/main.ece75643d67e9bcbe226.hot-update.js": "/js/main.ece75643d67e9bcbe226.hot-update.js", + "/js/main.6a23a146600381ea9131.hot-update.js": "/js/main.6a23a146600381ea9131.hot-update.js", + "/js/main.bc3bf0a6e8cc685e89a1.hot-update.js": "/js/main.bc3bf0a6e8cc685e89a1.hot-update.js", + "/js/main.afece1ee9777a0f09f92.hot-update.js": "/js/main.afece1ee9777a0f09f92.hot-update.js", + "/js/main.318eeefc5c617a59ee6d.hot-update.js": "/js/main.318eeefc5c617a59ee6d.hot-update.js", + "/js/main.755f6694f38c8c77bd27.hot-update.js": "/js/main.755f6694f38c8c77bd27.hot-update.js", + "/js/main.f18d478e890997d4f4da.hot-update.js": "/js/main.f18d478e890997d4f4da.hot-update.js", + "/js/main.db0f3a4f426d3990e240.hot-update.js": "/js/main.db0f3a4f426d3990e240.hot-update.js", + "/js/main.a405076120f1f39d6d6d.hot-update.js": "/js/main.a405076120f1f39d6d6d.hot-update.js", + "/js/main.6cba9147fd829edb958f.hot-update.js": "/js/main.6cba9147fd829edb958f.hot-update.js", + "/js/main.cd2fe4e3030ca6acc4e1.hot-update.js": "/js/main.cd2fe4e3030ca6acc4e1.hot-update.js", + "/js/main.5cded47a7b477704e2eb.hot-update.js": "/js/main.5cded47a7b477704e2eb.hot-update.js", + "/js/main.c7486336a6790360832d.hot-update.js": "/js/main.c7486336a6790360832d.hot-update.js", + "/js/main.f7016f686c1057bc13e3.hot-update.js": "/js/main.f7016f686c1057bc13e3.hot-update.js", + "/js/main.17c68ee60b815575d2f9.hot-update.js": "/js/main.17c68ee60b815575d2f9.hot-update.js", + "/js/main.86a4ab5670f7d5cabb35.hot-update.js": "/js/main.86a4ab5670f7d5cabb35.hot-update.js", + "/js/main.4dea91e54b0a38bba6e7.hot-update.js": "/js/main.4dea91e54b0a38bba6e7.hot-update.js", + "/js/main.feb31c4f536800f14886.hot-update.js": "/js/main.feb31c4f536800f14886.hot-update.js", + "/js/main.c47820cef3354299e493.hot-update.js": "/js/main.c47820cef3354299e493.hot-update.js", + "/js/main.5a3111773041d077914c.hot-update.js": "/js/main.5a3111773041d077914c.hot-update.js", + "/js/main.6c1dc0ab6b48b7fb8d0f.hot-update.js": "/js/main.6c1dc0ab6b48b7fb8d0f.hot-update.js", + "/js/main.acb7922f38d63ffedb8c.hot-update.js": "/js/main.acb7922f38d63ffedb8c.hot-update.js", + "/js/main.a53e0c7d11808569d268.hot-update.js": "/js/main.a53e0c7d11808569d268.hot-update.js", + "/js/main.a46a827c478e8ab2393e.hot-update.js": "/js/main.a46a827c478e8ab2393e.hot-update.js", + "/js/main.701befe377412289c1e7.hot-update.js": "/js/main.701befe377412289c1e7.hot-update.js", + "/js/main.4b1e0972a8490afd2480.hot-update.js": "/js/main.4b1e0972a8490afd2480.hot-update.js", + "/js/main.a12d0df8441fe0b1b64e.hot-update.js": "/js/main.a12d0df8441fe0b1b64e.hot-update.js", + "/js/main.8a13cdc450f54124a2e8.hot-update.js": "/js/main.8a13cdc450f54124a2e8.hot-update.js", + "/js/main.86ad86b8ab0ac84651d7.hot-update.js": "/js/main.86ad86b8ab0ac84651d7.hot-update.js", + "/js/main.d4311ae5421503ee73ba.hot-update.js": "/js/main.d4311ae5421503ee73ba.hot-update.js", + "/js/main.a12f2b869039233ec7d1.hot-update.js": "/js/main.a12f2b869039233ec7d1.hot-update.js", + "/js/main.4331e216c81ad63d68d2.hot-update.js": "/js/main.4331e216c81ad63d68d2.hot-update.js", + "/js/main.a9f4434e2ef7b9e029f9.hot-update.js": "/js/main.a9f4434e2ef7b9e029f9.hot-update.js", + "/js/main.f4cf1d836ab8fad25b18.hot-update.js": "/js/main.f4cf1d836ab8fad25b18.hot-update.js", + "/js/main.714ab8accd45fca0352c.hot-update.js": "/js/main.714ab8accd45fca0352c.hot-update.js", + "/js/main.f4eb0b9755cffa4dfcc7.hot-update.js": "/js/main.f4eb0b9755cffa4dfcc7.hot-update.js", + "/js/main.e970c4c1f36844c2977f.hot-update.js": "/js/main.e970c4c1f36844c2977f.hot-update.js", + "/js/main.78570685fbf7d85246f2.hot-update.js": "/js/main.78570685fbf7d85246f2.hot-update.js", + "/js/main.fb03325bcaa8d4d98090.hot-update.js": "/js/main.fb03325bcaa8d4d98090.hot-update.js", + "/js/main.4d26215591306c839618.hot-update.js": "/js/main.4d26215591306c839618.hot-update.js", + "/js/main.2d1f539b131e5873a485.hot-update.js": "/js/main.2d1f539b131e5873a485.hot-update.js", + "/js/main.835a770181065c1b9397.hot-update.js": "/js/main.835a770181065c1b9397.hot-update.js", + "/js/main.09f9dab42bcfa1634f4d.hot-update.js": "/js/main.09f9dab42bcfa1634f4d.hot-update.js", + "/js/main.8a70e542c3e3304535a4.hot-update.js": "/js/main.8a70e542c3e3304535a4.hot-update.js", + "/js/main.325adf8cf5e1a83126a5.hot-update.js": "/js/main.325adf8cf5e1a83126a5.hot-update.js", + "/js/main.f961b6e9d077a442035a.hot-update.js": "/js/main.f961b6e9d077a442035a.hot-update.js", + "/js/main.60d318b86ef1942116fc.hot-update.js": "/js/main.60d318b86ef1942116fc.hot-update.js", + "/js/main.fa7aca50202d520eff5c.hot-update.js": "/js/main.fa7aca50202d520eff5c.hot-update.js", + "/js/main.c61bd9bd5f3bd0f7bfaf.hot-update.js": "/js/main.c61bd9bd5f3bd0f7bfaf.hot-update.js", + "/js/main.ebf01927b1c1132074bb.hot-update.js": "/js/main.ebf01927b1c1132074bb.hot-update.js", + "/js/main.73ceb4ddc97dd83e3e9d.hot-update.js": "/js/main.73ceb4ddc97dd83e3e9d.hot-update.js", + "/js/main.d59fcd1a37bfee22bf4f.hot-update.js": "/js/main.d59fcd1a37bfee22bf4f.hot-update.js", + "/js/main.94b332e4c7a0031ed8ef.hot-update.js": "/js/main.94b332e4c7a0031ed8ef.hot-update.js", + "/js/main.578348615f140107a146.hot-update.js": "/js/main.578348615f140107a146.hot-update.js", + "/js/main.4d0d19eb2776ac141cf3.hot-update.js": "/js/main.4d0d19eb2776ac141cf3.hot-update.js", + "/js/main.c6c7699155e707c83638.hot-update.js": "/js/main.c6c7699155e707c83638.hot-update.js", + "/js/main.cc2b80170d644f9fb024.hot-update.js": "/js/main.cc2b80170d644f9fb024.hot-update.js", + "/js/main.7d835be6842b8c9b22ac.hot-update.js": "/js/main.7d835be6842b8c9b22ac.hot-update.js", + "/js/main.c48c478a7083284384b4.hot-update.js": "/js/main.c48c478a7083284384b4.hot-update.js", + "/js/main.0a033e7788ae142bef26.hot-update.js": "/js/main.0a033e7788ae142bef26.hot-update.js", + "/js/main.0b7dfc2f94ca7c20da89.hot-update.js": "/js/main.0b7dfc2f94ca7c20da89.hot-update.js", + "/js/main.321b6748d866ee25e6a0.hot-update.js": "/js/main.321b6748d866ee25e6a0.hot-update.js", + "/js/main.0fc25b4b50854ab03ecb.hot-update.js": "/js/main.0fc25b4b50854ab03ecb.hot-update.js", + "/js/main.d4eb7381630781b7f83b.hot-update.js": "/js/main.d4eb7381630781b7f83b.hot-update.js", + "/js/main.dbf7b9a6b03dd3e764f5.hot-update.js": "/js/main.dbf7b9a6b03dd3e764f5.hot-update.js", + "/js/main.f2d175ee3ad7394744f3.hot-update.js": "/js/main.f2d175ee3ad7394744f3.hot-update.js", + "/js/main.7097f18bafbb2c782e34.hot-update.js": "/js/main.7097f18bafbb2c782e34.hot-update.js", + "/js/main.e74278efede5c77fb59a.hot-update.js": "/js/main.e74278efede5c77fb59a.hot-update.js", + "/js/main.b16252a3f321aa5f638c.hot-update.js": "/js/main.b16252a3f321aa5f638c.hot-update.js", + "/js/main.65d67c907472cea149a2.hot-update.js": "/js/main.65d67c907472cea149a2.hot-update.js", + "/js/main.d32086da1adce30a365c.hot-update.js": "/js/main.d32086da1adce30a365c.hot-update.js", + "/js/main.2abe2dd3690d4223aae6.hot-update.js": "/js/main.2abe2dd3690d4223aae6.hot-update.js", + "/js/main.34d101ff4e9c0f3c9f62.hot-update.js": "/js/main.34d101ff4e9c0f3c9f62.hot-update.js", + "/js/main.db8a2c676ae81a86ac07.hot-update.js": "/js/main.db8a2c676ae81a86ac07.hot-update.js", + "/js/main.3e10515c75b547edbd84.hot-update.js": "/js/main.3e10515c75b547edbd84.hot-update.js", + "/js/main.a21b2c4011d251b159dc.hot-update.js": "/js/main.a21b2c4011d251b159dc.hot-update.js", + "/js/main.29892a9f1673876f812a.hot-update.js": "/js/main.29892a9f1673876f812a.hot-update.js", + "/js/main.90e4e646ca4687f65d71.hot-update.js": "/js/main.90e4e646ca4687f65d71.hot-update.js", + "/js/main.2f329e24c50c195b7655.hot-update.js": "/js/main.2f329e24c50c195b7655.hot-update.js", + "/js/main.a8a8ecc1aa6706cf3eb7.hot-update.js": "/js/main.a8a8ecc1aa6706cf3eb7.hot-update.js", + "/js/main.e03c44a34c09251882cb.hot-update.js": "/js/main.e03c44a34c09251882cb.hot-update.js", + "/js/main.1c1d02f7cd7ffae319bd.hot-update.js": "/js/main.1c1d02f7cd7ffae319bd.hot-update.js", + "/js/main.a1fed26b6036e6849112.hot-update.js": "/js/main.a1fed26b6036e6849112.hot-update.js", + "/js/main.c4018662de1655065788.hot-update.js": "/js/main.c4018662de1655065788.hot-update.js", + "/js/main.330cb26c0bc23a53c884.hot-update.js": "/js/main.330cb26c0bc23a53c884.hot-update.js", + "/js/main.33a43c4c29198be879c5.hot-update.js": "/js/main.33a43c4c29198be879c5.hot-update.js", + "/js/main.60e7443fa7dee86c85d8.hot-update.js": "/js/main.60e7443fa7dee86c85d8.hot-update.js", + "/js/main.3e4d2dc845d6e6fd27a1.hot-update.js": "/js/main.3e4d2dc845d6e6fd27a1.hot-update.js", + "/js/main.c072ce5e1e8ee60ff114.hot-update.js": "/js/main.c072ce5e1e8ee60ff114.hot-update.js", + "/js/main.435a7170ae4c41a507bf.hot-update.js": "/js/main.435a7170ae4c41a507bf.hot-update.js", + "/js/main.3e5be2b0dcd70adc5f07.hot-update.js": "/js/main.3e5be2b0dcd70adc5f07.hot-update.js", + "/js/main.c1e5ceaa30c4a222234b.hot-update.js": "/js/main.c1e5ceaa30c4a222234b.hot-update.js", + "/js/main.430a9eb6b1382f42d8f6.hot-update.js": "/js/main.430a9eb6b1382f42d8f6.hot-update.js", + "/js/main.c962104d9cc0e244dbcf.hot-update.js": "/js/main.c962104d9cc0e244dbcf.hot-update.js", + "/js/main.631a573d0816d40f99b3.hot-update.js": "/js/main.631a573d0816d40f99b3.hot-update.js", + "/js/main.c4966c4a540e5ed0e0fe.hot-update.js": "/js/main.c4966c4a540e5ed0e0fe.hot-update.js", + "/js/main.a7e2cb4059892a5ce2d1.hot-update.js": "/js/main.a7e2cb4059892a5ce2d1.hot-update.js", + "/js/main.c1a278d80d8245b8dbed.hot-update.js": "/js/main.c1a278d80d8245b8dbed.hot-update.js", + "/js/main.6f0d43bf3f52d2c4ac14.hot-update.js": "/js/main.6f0d43bf3f52d2c4ac14.hot-update.js", + "/js/main.e784ffe94b54d5a998ab.hot-update.js": "/js/main.e784ffe94b54d5a998ab.hot-update.js", + "/js/main.dea1f26cd34f991d67e1.hot-update.js": "/js/main.dea1f26cd34f991d67e1.hot-update.js", + "/js/main.061ee62ce1874101ac2a.hot-update.js": "/js/main.061ee62ce1874101ac2a.hot-update.js", + "/js/main.85875fb8da3b6c2c9b22.hot-update.js": "/js/main.85875fb8da3b6c2c9b22.hot-update.js", + "/js/main.db7c288c6b109145c54f.hot-update.js": "/js/main.db7c288c6b109145c54f.hot-update.js", + "/js/main.70d8638e1b3444f4775f.hot-update.js": "/js/main.70d8638e1b3444f4775f.hot-update.js", + "/js/main.0d29c7bec145fcf354fb.hot-update.js": "/js/main.0d29c7bec145fcf354fb.hot-update.js", + "/js/main.9cd13670523ef48d28e6.hot-update.js": "/js/main.9cd13670523ef48d28e6.hot-update.js", + "/js/main.47ee00b321282e6ae987.hot-update.js": "/js/main.47ee00b321282e6ae987.hot-update.js", + "/js/main.5ef029b918e611550c36.hot-update.js": "/js/main.5ef029b918e611550c36.hot-update.js", + "/js/main.f6719ad9576c258cf584.hot-update.js": "/js/main.f6719ad9576c258cf584.hot-update.js", + "/js/main.e7ccbb9786d4116e87e5.hot-update.js": "/js/main.e7ccbb9786d4116e87e5.hot-update.js", + "/js/main.9ca0d30ea69682a73a3f.hot-update.js": "/js/main.9ca0d30ea69682a73a3f.hot-update.js", + "/js/main.aea23c89a4d4cb490f38.hot-update.js": "/js/main.aea23c89a4d4cb490f38.hot-update.js", + "/js/main.2a2df9603b1822c15a5e.hot-update.js": "/js/main.2a2df9603b1822c15a5e.hot-update.js", + "/js/main.489f68ca99fda16cb5d1.hot-update.js": "/js/main.489f68ca99fda16cb5d1.hot-update.js", + "/js/main.597a3d62f124fa4b1495.hot-update.js": "/js/main.597a3d62f124fa4b1495.hot-update.js", + "/js/main.d5392b120f1eb2e4c816.hot-update.js": "/js/main.d5392b120f1eb2e4c816.hot-update.js", + "/js/main.a7ae925207be44232c5c.hot-update.js": "/js/main.a7ae925207be44232c5c.hot-update.js", + "/js/main.bb42904cd41c18389ec8.hot-update.js": "/js/main.bb42904cd41c18389ec8.hot-update.js", + "/js/main.fe643a9998fdca67359e.hot-update.js": "/js/main.fe643a9998fdca67359e.hot-update.js", + "/js/main.daf27f047d4b8810e2cf.hot-update.js": "/js/main.daf27f047d4b8810e2cf.hot-update.js", + "/js/main.986dd7f8fc929c9c0b4f.hot-update.js": "/js/main.986dd7f8fc929c9c0b4f.hot-update.js", + "/js/main.77d2a6871e82cc5d0d09.hot-update.js": "/js/main.77d2a6871e82cc5d0d09.hot-update.js", + "/js/main.49faf863ade9028d0d34.hot-update.js": "/js/main.49faf863ade9028d0d34.hot-update.js", + "/js/main.0ffae012a756027e52bd.hot-update.js": "/js/main.0ffae012a756027e52bd.hot-update.js", + "/js/main.df15a1f233ee639ed364.hot-update.js": "/js/main.df15a1f233ee639ed364.hot-update.js", + "/js/main.3d8a3e25947961464dcc.hot-update.js": "/js/main.3d8a3e25947961464dcc.hot-update.js", + "/js/main.ef757ee6853c9f37b38c.hot-update.js": "/js/main.ef757ee6853c9f37b38c.hot-update.js", + "/js/main.009a2af69a329c5f4ced.hot-update.js": "/js/main.009a2af69a329c5f4ced.hot-update.js", + "/js/main.3eba90d0a4c80f1cc076.hot-update.js": "/js/main.3eba90d0a4c80f1cc076.hot-update.js", + "/js/main.530a0e768d44eaffc432.hot-update.js": "/js/main.530a0e768d44eaffc432.hot-update.js", + "/js/main.2e446e138db18ddc31a6.hot-update.js": "/js/main.2e446e138db18ddc31a6.hot-update.js", + "/js/main.d53c4114c93122303427.hot-update.js": "/js/main.d53c4114c93122303427.hot-update.js" } diff --git a/resources/js/App.vue b/resources/js/App.vue index 249c24d8..61576b42 100644 --- a/resources/js/App.vue +++ b/resources/js/App.vue @@ -74,7 +74,7 @@ 'isLogged', 'isGuest' ]), layout() { - if (includes(['PurchaseCode', 'StripeCredentials', 'AppSetup', 'EnvironmentSetup', 'BillingsDetail', 'SubscriptionPlans', 'Database', 'VerifyByPassword', 'SharedPage', 'NotFoundShared', 'SignIn', 'SignUp', 'ForgottenPassword', 'CreateNewPassword'], this.$route.name)) { + if (includes(['AdminAccount', 'PurchaseCode', 'SubscriptionService', 'StripeCredentials', 'AppSetup', 'EnvironmentSetup', 'BillingsDetail', 'SubscriptionPlans', 'Database', 'VerifyByPassword', 'SharedPage', 'NotFoundShared', 'SignIn', 'SignUp', 'ForgottenPassword', 'CreateNewPassword'], this.$route.name)) { return 'unauthorized' } diff --git a/resources/js/components/Others/Forms/ImageInput.vue b/resources/js/components/Others/Forms/ImageInput.vue index 25da4331..7afe91a8 100644 --- a/resources/js/components/Others/Forms/ImageInput.vue +++ b/resources/js/components/Others/Forms/ImageInput.vue @@ -14,7 +14,7 @@ />
- + {{ $t('input_image.title') }} @@ -26,7 +26,7 @@ @@ -21,9 +22,18 @@ background: $light_background; text-align: left; + &.error { + background: rgba($danger, 0.1); + + p, a { + color: $danger; + } + } + p { @include font-size(15); line-height: 1.6; + word-break: break-all; } a { diff --git a/resources/js/router.js b/resources/js/router.js index 1f5790d4..fd26d4e7 100644 --- a/resources/js/router.js +++ b/resources/js/router.js @@ -59,10 +59,12 @@ import SetupWizard from './views/SetupWizard' import Database from './views/SetupWizard/Database' import AppSetup from './views/SetupWizard/AppSetup' import PurchaseCode from './views/SetupWizard/PurchaseCode' +import AdminAccount from './views/SetupWizard/AdminAccount' import BillingsDetail from './views/SetupWizard/BillingsDetail' import EnvironmentSetup from './views/SetupWizard/EnvironmentSetup' import StripeCredentials from './views/SetupWizard/StripeCredentials' import SubscriptionPlans from './views/SetupWizard/SubscriptionPlans' +import SubscriptionService from './views/SetupWizard/SubscriptionService' Vue.use(Router) @@ -465,6 +467,14 @@ const routesMaintenance = [ requiresAuth: false, }, }, + { + name: 'SubscriptionService', + path: '/setup-wizard/subscription-service', + component: SubscriptionService, + meta: { + requiresAuth: false, + }, + }, { name: 'StripeCredentials', path: '/setup-wizard/stripe-credentials', @@ -475,7 +485,7 @@ const routesMaintenance = [ }, { name: 'BillingsDetail', - path: '/setup-wizard/billings', + path: '/setup-wizard/stripe-billings', component: BillingsDetail, meta: { requiresAuth: false, @@ -483,7 +493,7 @@ const routesMaintenance = [ }, { name: 'SubscriptionPlans', - path: '/setup-wizard/subscription-plans', + path: '/setup-wizard/stripe-plans', component: SubscriptionPlans, meta: { requiresAuth: false, @@ -505,6 +515,14 @@ const routesMaintenance = [ requiresAuth: false, }, }, + { + name: 'AdminAccount', + path: '/setup-wizard/admin-setup', + component: AdminAccount, + meta: { + requiresAuth: false, + }, + }, ] }, ] diff --git a/resources/js/views/SetupWIzard/AdminAccount.vue b/resources/js/views/SetupWIzard/AdminAccount.vue new file mode 100644 index 00000000..d5b2ea8b --- /dev/null +++ b/resources/js/views/SetupWIzard/AdminAccount.vue @@ -0,0 +1,202 @@ + + + + + diff --git a/resources/js/views/SetupWIzard/AppSetup.vue b/resources/js/views/SetupWIzard/AppSetup.vue index 9f79fff6..c3abdcae 100644 --- a/resources/js/views/SetupWIzard/AppSetup.vue +++ b/resources/js/views/SetupWIzard/AppSetup.vue @@ -9,20 +9,97 @@

Set up your application appearance, analytics, etc.

- + General Settings
- - - + + + {{ errors[0] }}
+
+ + + + {{ errors[0] }} + +
+ +
+ + + + +
+ +
+ + + + +
Others Information +
+ + + + {{ errors[0] }} + +
+ +
+ + + + {{ errors[0] }} + +
+ +
+
+
+
+ +
+ +
+
+
+ +
+ + + + {{ errors[0] }} + +
+ +
+
+
+
+ +
+ +
+
+
+
@@ -36,11 +113,13 @@ import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full' import AuthContentWrapper from '@/components/Auth/AuthContentWrapper' import SelectInput from '@/components/Others/Forms/SelectInput' + import SwitchInput from '@/components/Others/Forms/SwitchInput' + import ImageInput from '@/components/Others/Forms/ImageInput' import FormLabel from '@/components/Others/Forms/FormLabel' import InfoBox from '@/components/Others/Forms/InfoBox' import AuthContent from '@/components/Auth/AuthContent' import AuthButton from '@/components/Auth/AuthButton' - import { SettingsIcon } from 'vue-feather-icons' + import {SettingsIcon} from 'vue-feather-icons' import {required} from 'vee-validate/dist/rules' import {mapGetters} from 'vuex' import axios from 'axios' @@ -53,7 +132,9 @@ ValidationObserver, SettingsIcon, SelectInput, + SwitchInput, AuthContent, + ImageInput, AuthButton, FormLabel, required, @@ -62,51 +143,68 @@ data() { return { isLoading: false, - storageServiceList: [ - { - label: 'Local Driver', - value: 'local', - }, - { - label: 'Amazon Web Services S3', - value: 's3', - }, - { - label: 'Digital Ocean Spaces', - value: 'spaces', - }, - ], - encryptionList: [ - { - label: 'TLS', - value: 'tls', - }, - { - label: 'SSL', - value: 'ssl', - }, - ], - storage: { - driver: 'local', - key: '', - secret: '', - endpoint: '', - region: '', - bucket: '', + app: { + title: '', + description: '', + logo: undefined, + favicon: undefined, + contactMail: '', + googleAnalytics: '', + defaultStorage: '', + userRegistration: 1, + storageLimitation: 1, }, - mail: { - driver: '', - host: '', - port: '', - username: '', - password: '', - encryption: '', - } } }, methods: { async appSetupSubmit() { - this.$router.push({name: 'AppSetup'}) + + // Validate fields + const isValid = await this.$refs.appSetup.validate(); + + if (!isValid) return; + + // Start loading + this.isLoading = true + + // Create form + let formData = new FormData() + + // Add image to form + formData.append('title', this.app.title) + formData.append('description', this.app.description) + formData.append('contactMail', this.app.contactMail) + formData.append('googleAnalytics', this.app.googleAnalytics) + formData.append('defaultStorage', this.app.defaultStorage) + formData.append('userRegistration', this.app.userRegistration) + formData.append('storageLimitation', this.app.storageLimitation) + + if (this.app.logo) + formData.append('logo', this.app.logo) + + if (this.app.favicon) + formData.append('favicon', this.app.favicon) + + // Send request to get verify account + axios + .post('/api/setup/app-setup', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + } + }) + .then(response => { + + // End loading + this.isLoading = false + + // Redirect to next step + this.$router.push({name: 'AdminAccount'}) + }) + .catch(error => { + + // End loading + this.isLoading = false + }) }, }, created() { @@ -117,7 +215,6 @@ diff --git a/resources/js/views/Upgrade/UpgradeBilling.vue b/resources/js/views/Upgrade/UpgradeBilling.vue index 1a908284..3596343b 100644 --- a/resources/js/views/Upgrade/UpgradeBilling.vue +++ b/resources/js/views/Upgrade/UpgradeBilling.vue @@ -327,8 +327,6 @@ // If user don't have credit card, register new if (!this.defaultPaymentMethod || this.payByNewCard) { - console.log('Payment by new card'); - const {setupIntent, error} = await stripe.confirmCardSetup(this.clientSecret, { payment_method: { card: card, @@ -367,8 +365,6 @@ // if user has credit card if (this.defaultPaymentMethod && !this.payByNewCard) { - console.log('Payment by default card'); - axios .post('/api/subscription/upgrade', { billing: this.billing, diff --git a/resources/sass/vue-file-manager/_setup_wizard.scss b/resources/sass/vue-file-manager/_setup_wizard.scss index 482a2838..e9e3fdd9 100644 --- a/resources/sass/vue-file-manager/_setup_wizard.scss +++ b/resources/sass/vue-file-manager/_setup_wizard.scss @@ -1,3 +1,9 @@ +.content-headline { + max-width: 630px; + margin-left: auto; + margin-right: auto; +} + .auth-form { input { diff --git a/routes/api.php b/routes/api.php index 831b3618..4fded14b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -16,6 +16,20 @@ |-------------------------------------------------------------------------- */ +// Setup Wizard +Route::group(['middleware' => ['api'], 'prefix' => 'setup'], function () { + Route::post('/purchase-code', 'General\SetupWizardController@verify_purchase_code'); + Route::post('/database', 'General\SetupWizardController@setup_database'); + + Route::post('/stripe-credentials', 'General\SetupWizardController@store_stripe_credentials'); + Route::post('/stripe-billings', 'General\SetupWizardController@store_stripe_billings'); + Route::post('/stripe-plans', 'General\SetupWizardController@store_stripe_plans'); + + Route::post('/environment-setup', 'General\SetupWizardController@store_environment_setup'); + Route::post('/app-setup', 'General\SetupWizardController@store_app_settings'); + Route::post('/admin-setup', 'General\SetupWizardController@create_admin_account'); +}); + // Plans Route::group(['middleware' => ['api'], 'prefix' => 'public'], function () { Route::get('/pricing', 'General\PricingController@index'); diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore deleted file mode 100644 index 01e4a6cd..00000000 --- a/storage/framework/cache/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!data/ -!.gitignore