├── .env.example ├── .gitattributes ├── .gitignore ├── app ├── Auth │ └── TwoFactor │ │ └── Authenticatable.php ├── Billing │ └── EmailInvoiceNotifier.php ├── Console │ ├── Commands │ │ └── Inspire.php │ └── Kernel.php ├── Contracts │ ├── Auth │ │ └── TwoFactor │ │ │ ├── Authenticatable.php │ │ │ └── Provider.php │ ├── Billing │ │ └── InvoiceNotifier.php │ └── Repositories │ │ ├── TeamRepository.php │ │ └── UserRepository.php ├── Events │ ├── Event.php │ ├── Team │ │ ├── Created.php │ │ └── Deleting.php │ └── User │ │ ├── Event.php │ │ ├── JoinedTeam.php │ │ ├── ProfileUpdated.php │ │ ├── Registered.php │ │ ├── RemovedFromTeam.php │ │ ├── Subscribed.php │ │ ├── SubscriptionCancelled.php │ │ ├── SubscriptionPlanChanged.php │ │ └── SubscriptionResumed.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── API │ │ │ ├── InvitationController.php │ │ │ ├── SubscriptionController.php │ │ │ ├── TeamController.php │ │ │ └── UserController.php │ │ ├── Auth │ │ │ ├── AuthController.php │ │ │ └── PasswordController.php │ │ ├── Controller.php │ │ ├── HomeController.php │ │ ├── Settings │ │ │ ├── DashboardController.php │ │ │ ├── InvitationController.php │ │ │ ├── ProfileController.php │ │ │ ├── SecurityController.php │ │ │ ├── SubscriptionController.php │ │ │ └── TeamController.php │ │ ├── Stripe │ │ │ └── WebhookController.php │ │ ├── TermsController.php │ │ └── WelcomeController.php │ ├── Kernel.php │ ├── Middleware │ │ ├── Authenticate.php │ │ ├── EncryptCookies.php │ │ ├── RedirectIfAuthenticated.php │ │ └── VerifyCsrfToken.php │ ├── Requests │ │ └── Request.php │ └── routes.php ├── InteractsWithSparkHooks.php ├── Jobs │ └── Job.php ├── Listeners │ └── .gitkeep ├── Policies │ └── .gitkeep ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── EventServiceProvider.php │ ├── RouteServiceProvider.php │ └── SparkServiceProvider.php ├── Repositories │ ├── TeamRepository.php │ └── UserRepository.php ├── Services │ └── Auth │ │ └── TwoFactor │ │ └── Authy.php ├── Spark.php ├── Subscriptions │ ├── Coupon.php │ ├── Plan.php │ └── Plans.php ├── Team.php ├── Teams │ ├── CanJoinTeams.php │ ├── Invitation.php │ └── Team.php ├── User.php └── Ux │ └── Settings │ ├── DashboardTabs.php │ ├── Tab.php │ ├── Tabs.php │ └── TeamTabs.php ├── artisan ├── bootstrap ├── app.php ├── autoload.php └── cache │ └── .gitignore ├── composer.json ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── compile.php ├── database.php ├── filesystems.php ├── mail.php ├── queue.php ├── services.php ├── session.php └── view.php ├── database ├── .gitignore ├── factories │ └── ModelFactory.php ├── migrations │ ├── .gitkeep │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2014_10_12_100000_create_subscriptions_table.php │ └── 2014_10_12_200000_create_teams_tables.php └── seeds │ ├── .gitkeep │ └── DatabaseSeeder.php ├── gulpfile.js ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── build │ ├── css │ │ ├── app-d5481a85bb.css │ │ └── app.css.map │ ├── js │ │ ├── app-4377a2b0f7.js │ │ └── app.js.map │ └── rev-manifest.json ├── css │ ├── app.css │ └── app.css.map ├── favicon.ico ├── index.php ├── js │ ├── app.js │ └── app.js.map ├── robots.txt └── web.config ├── readme.md ├── resources ├── assets │ ├── js │ │ ├── .gitignore │ │ ├── app.js │ │ ├── auth │ │ │ └── registration │ │ │ │ ├── simple.js │ │ │ │ └── subscription.js │ │ ├── common │ │ │ └── errors.js │ │ ├── core │ │ │ ├── bootstrap.js │ │ │ └── components.js │ │ ├── forms │ │ │ ├── bootstrap.js │ │ │ ├── components.js │ │ │ ├── errors.js │ │ │ ├── http.js │ │ │ └── instance.js │ │ ├── nav.js │ │ ├── package.json │ │ ├── settings │ │ │ ├── dashboard.js │ │ │ ├── dashboard │ │ │ │ ├── profile │ │ │ │ │ └── basics.js │ │ │ │ ├── security │ │ │ │ │ ├── password.js │ │ │ │ │ └── two-factor.js │ │ │ │ ├── subscription.js │ │ │ │ └── teams.js │ │ │ ├── team.js │ │ │ └── team │ │ │ │ ├── membership.js │ │ │ │ ├── membership │ │ │ │ └── edit-team-member.js │ │ │ │ └── owner │ │ │ │ └── basics.js │ │ └── spark.js │ └── sass │ │ ├── app.scss │ │ ├── essentials.scss │ │ └── themes │ │ └── spark │ │ ├── components │ │ ├── settings.scss │ │ ├── splash.scss │ │ ├── subscription.scss │ │ └── terms.scss │ │ ├── elements │ │ ├── alerts.scss │ │ ├── buttons.scss │ │ ├── forms.scss │ │ ├── modals.scss │ │ ├── nav.scss │ │ ├── panels.scss │ │ ├── tables.scss │ │ └── tooltips.scss │ │ ├── spark.scss │ │ └── variables.scss ├── lang │ └── en │ │ ├── auth.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php └── views │ ├── auth │ ├── login.blade.php │ ├── password │ │ ├── email.blade.php │ │ └── reset.blade.php │ ├── registration │ │ ├── simple.blade.php │ │ ├── simple │ │ │ └── basics.blade.php │ │ ├── subscription.blade.php │ │ └── subscription │ │ │ ├── basics.blade.php │ │ │ ├── billing.blade.php │ │ │ ├── coupon.blade.php │ │ │ ├── inspiration.blade.php │ │ │ ├── invitation.blade.php │ │ │ └── plans │ │ │ ├── plan.blade.php │ │ │ ├── selected.blade.php │ │ │ └── selector.blade.php │ └── token.blade.php │ ├── common │ ├── error-alert.blade.php │ └── footer.blade.php │ ├── emails │ ├── auth │ │ └── password │ │ │ └── email.blade.php │ ├── billing │ │ └── invoice.blade.php │ └── team │ │ └── invitations │ │ ├── existing.blade.php │ │ └── new.blade.php │ ├── errors │ └── 503.blade.php │ ├── home.blade.php │ ├── layouts │ └── app.blade.php │ ├── nav │ ├── authenticated.blade.php │ ├── guest.blade.php │ └── settings.blade.php │ ├── scripts │ └── globals.blade.php │ ├── settings │ ├── dashboard.blade.php │ ├── tabs │ │ ├── profile.blade.php │ │ ├── profile │ │ │ └── basics.blade.php │ │ ├── security.blade.php │ │ ├── security │ │ │ ├── password.blade.php │ │ │ └── two-factor.blade.php │ │ ├── subscription.blade.php │ │ ├── subscription │ │ │ ├── cancel.blade.php │ │ │ ├── card.blade.php │ │ │ ├── change.blade.php │ │ │ ├── coupon.blade.php │ │ │ ├── invoices │ │ │ │ ├── history.blade.php │ │ │ │ └── vat.blade.php │ │ │ ├── modals │ │ │ │ ├── cancel.blade.php │ │ │ │ ├── change.blade.php │ │ │ │ └── change │ │ │ │ │ └── plan.blade.php │ │ │ ├── resume.blade.php │ │ │ ├── subscribe.blade.php │ │ │ └── subscribe │ │ │ │ ├── plan.blade.php │ │ │ │ └── selector.blade.php │ │ └── teams.blade.php │ ├── team.blade.php │ └── team │ │ └── tabs │ │ ├── membership.blade.php │ │ ├── membership │ │ └── modals │ │ │ └── edit-team-member.blade.php │ │ ├── owner.blade.php │ │ └── owner │ │ └── basics.blade.php │ ├── terms.blade.php │ ├── vendor │ └── .gitkeep │ └── welcome.blade.php ├── server.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── terms.md └── tests ├── ExampleTest.php └── TestCase.php /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | APP_DEBUG=true 3 | APP_KEY=SomeRandomString 4 | APP_URL=http://localhost 5 | 6 | DB_CONNECTION=mysql 7 | DB_HOST=127.0.0.1 8 | DB_PORT=3306 9 | DB_DATABASE=homestead 10 | DB_USERNAME=homestead 11 | DB_PASSWORD=secret 12 | 13 | CACHE_DRIVER=file 14 | SESSION_DRIVER=file 15 | QUEUE_DRIVER=sync 16 | 17 | REDIS_HOST=127.0.0.1 18 | REDIS_PASSWORD=null 19 | REDIS_PORT=6379 20 | 21 | MAIL_DRIVER=smtp 22 | MAIL_HOST=mailtrap.io 23 | MAIL_PORT=2525 24 | MAIL_USERNAME=null 25 | MAIL_PASSWORD=null 26 | MAIL_ENCRYPTION=null 27 | 28 | AUTHY_KEY= 29 | 30 | STRIPE_KEY= 31 | STRIPE_SECRET= 32 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.less linguist-vendored 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /node_modules 3 | /public/storage 4 | Homestead.yaml 5 | Homestead.json 6 | .env 7 | -------------------------------------------------------------------------------- /app/Auth/TwoFactor/Authenticatable.php: -------------------------------------------------------------------------------- 1 | email; 19 | } 20 | 21 | /** 22 | * Get the country code used for two-factor authentication. 23 | * 24 | * @return string 25 | */ 26 | public function getAuthCountryCode() 27 | { 28 | return $this->phone_country_code; 29 | } 30 | 31 | /** 32 | * Get the phone number used for two-factor authentication. 33 | * 34 | * @return string 35 | */ 36 | public function getAuthPhoneNumber() 37 | { 38 | return $this->phone_number; 39 | } 40 | 41 | /** 42 | * Set the country code and phone number used for two-factor authentication. 43 | * 44 | * @param string $countryCode 45 | * @param string $phoneNumber 46 | * @return void 47 | */ 48 | public function setAuthPhoneInformation($countryCode, $phoneNumber) 49 | { 50 | $this->phone_country_code = $countryCode; 51 | 52 | $this->phone_number = $phoneNumber; 53 | } 54 | 55 | /** 56 | * Get the two-factor provider options in array format. 57 | * 58 | * @return array 59 | */ 60 | public function getTwoFactorAuthProviderOptions() 61 | { 62 | return json_decode($this->two_factor_options, true) ?: []; 63 | } 64 | 65 | /** 66 | * Set the two-factor provider options in array format. 67 | * 68 | * @param array $options 69 | * @return void 70 | */ 71 | public function setTwoFactorAuthProviderOptions(array $options) 72 | { 73 | $this->two_factor_options = json_encode($options); 74 | } 75 | 76 | /** 77 | * Determine if the user is using two-factor authentication. 78 | * 79 | * @return bool 80 | */ 81 | public function getUsingTwoFactorAuthAttribute() 82 | { 83 | $options = $this->getTwoFactorAuthProviderOptions(); 84 | 85 | return isset($options['id']); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/Billing/EmailInvoiceNotifier.php: -------------------------------------------------------------------------------- 1 | 'Vendor', 25 | 'product' => 'Product', 26 | 'vat' => new HtmlString(nl2br(e($user->extra_billing_info))), 27 | ], Spark::generateInvoicesWith()); 28 | 29 | $data = compact('user', 'invoice', 'invoiceData'); 30 | 31 | Mail::send('emails.billing.invoice', $data, function ($message) use ($user, $invoice, $invoiceData) { 32 | $message->to($user->email, $user->name) 33 | ->subject('Your '.$invoiceData['product'].' Invoice') 34 | ->attachData($invoice->pdf($invoiceData), 'invoice.pdf'); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Console/Commands/Inspire.php: -------------------------------------------------------------------------------- 1 | comment(PHP_EOL.Inspiring::quote().PHP_EOL); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire') 28 | // ->hourly(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Contracts/Auth/TwoFactor/Authenticatable.php: -------------------------------------------------------------------------------- 1 | team = $team; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Events/Team/Deleting.php: -------------------------------------------------------------------------------- 1 | team = $team; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Events/User/Event.php: -------------------------------------------------------------------------------- 1 | user = $user; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Events/User/JoinedTeam.php: -------------------------------------------------------------------------------- 1 | user = $user; 38 | $this->team = $team; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Events/User/ProfileUpdated.php: -------------------------------------------------------------------------------- 1 | user = $user; 38 | $this->team = $team; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Events/User/Subscribed.php: -------------------------------------------------------------------------------- 1 | teams = $teams; 27 | 28 | $this->middleware('auth', ['except' => [ 29 | 'getInvitation', 30 | ]]); 31 | } 32 | 33 | /** 34 | * Get all of the pending invitations for the user. 35 | * 36 | * @param \Illuminate\Http\Request $request 37 | * @return \Illuminate\Http\Response 38 | */ 39 | public function getPendingInvitationsForUser(Request $request) 40 | { 41 | return $this->teams->getPendingInvitationsForUser($request->user()); 42 | } 43 | 44 | /** 45 | * Get the invitation for the given code. 46 | * 47 | * Used to display coupon during registration. 48 | * 49 | * @param string $code 50 | * @return \Illuminate\Http\Response 51 | */ 52 | public function getInvitation($code) 53 | { 54 | $model = config('auth.providers.users.model'); 55 | 56 | $model = get_class((new $model)->invitations() 57 | ->getQuery()->getModel()); 58 | 59 | $invitation = (new $model)->with('team.owner') 60 | ->where('token', $code)->firstOrFail(); 61 | 62 | if ($invitation->isExpired()) { 63 | $invitation->delete(); 64 | 65 | abort(404); 66 | } 67 | 68 | $invitation->team->setVisible(['name', 'owner']); 69 | 70 | $invitation->team->owner->setVisible(['name']); 71 | 72 | return $invitation; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/SubscriptionController.php: -------------------------------------------------------------------------------- 1 | middleware('auth', ['only' => [ 25 | 'getCouponForUser', 26 | ]]); 27 | } 28 | 29 | /** 30 | * Get all of the plans defined for the application. 31 | * 32 | * @return \Illuminate\Http\Response 33 | */ 34 | public function getPlans() 35 | { 36 | return response()->json(Spark::plans()); 37 | } 38 | 39 | /** 40 | * Get the coupon for a given code. 41 | * 42 | * Used for the registration page. 43 | * 44 | * @param string $code 45 | * @return \Illuminate\Http\Response 46 | */ 47 | public function getCoupon($code) 48 | { 49 | Stripe::setApiKey(config('services.stripe.secret')); 50 | 51 | if (count(Spark::plans()) === 0) { 52 | abort(404); 53 | } 54 | 55 | try { 56 | return response()->json( 57 | Coupon::fromStripeCoupon(StripeCoupon::retrieve($code)) 58 | ); 59 | } catch (Exception $e) { 60 | abort(404); 61 | } 62 | } 63 | 64 | /** 65 | * Get the current coupon for the authenticated user. 66 | * 67 | * Used to display current discount on settings -> subscription tab. 68 | * 69 | * @return \Illuminate\Http\Response 70 | */ 71 | public function getCouponForUser() 72 | { 73 | Stripe::setApiKey(config('services.stripe.secret')); 74 | 75 | if (count(Spark::plans()) === 0) { 76 | abort(404); 77 | } 78 | 79 | try { 80 | $customer = StripeCustomer::retrieve(Auth::user()->stripe_id); 81 | 82 | if ($customer->discount) { 83 | return response()->json( 84 | Coupon::fromStripeCoupon( 85 | StripeCoupon::retrieve($customer->discount->coupon->id) 86 | ) 87 | ); 88 | } else { 89 | abort(404); 90 | } 91 | } catch (Exception $e) { 92 | abort(404); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/TeamController.php: -------------------------------------------------------------------------------- 1 | teams = $teams; 30 | 31 | $this->middleware('auth', ['except' => [ 32 | 'getInvitation', 33 | ]]); 34 | } 35 | 36 | /** 37 | * Get the team for the given ID. 38 | * 39 | * @param \Illuminate\Http\Request $request 40 | * @param int $teamId 41 | * @return \Illuminate\Http\Response 42 | */ 43 | public function getTeam(Request $request, $teamId) 44 | { 45 | return $this->teams->getTeam($request->user(), $teamId); 46 | } 47 | 48 | /** 49 | * Get all of the teams for the user. 50 | * 51 | * @param \Illuminate\Http\Request $request 52 | * @return \Illuminate\Http\Response 53 | */ 54 | public function getAllTeamsForUser(Request $request) 55 | { 56 | return $this->teams->getAllTeamsForUser($request->user()); 57 | } 58 | 59 | /** 60 | * Get all of the available roles that may be assigned to team members. 61 | * 62 | * @return \Illuminate\Http\Response 63 | */ 64 | public function getTeamRoles() 65 | { 66 | $roles = []; 67 | 68 | foreach (Spark::roles() as $key => $value) { 69 | $roles[] = ['value' => $key, 'text' => $value]; 70 | } 71 | 72 | return response()->json($roles); 73 | } 74 | 75 | /** 76 | * Get all of the pending invitations for the user. 77 | * 78 | * @param \Illuminate\Http\Request $request 79 | * @return \Illuminate\Http\Response 80 | */ 81 | public function getPendingInvitationsForUser(Request $request) 82 | { 83 | return $this->teams->getPendingInvitationsForUser($request->user()); 84 | } 85 | 86 | /** 87 | * Get the invitation for the given code. 88 | * 89 | * @param string $code 90 | * @return \Illuminate\Http\Response 91 | */ 92 | public function getInvitation($code) 93 | { 94 | $model = config('auth.providers.users.model'); 95 | 96 | $model = get_class((new $model)->invitations() 97 | ->getQuery()->getModel()); 98 | 99 | $invitation = (new $model)->with('team.owner') 100 | ->where('token', $code)->firstOrFail(); 101 | 102 | if ($invitation->isExpired()) { 103 | $invitation->delete(); 104 | 105 | abort(404); 106 | } 107 | 108 | $invitation->team->setVisible(['name', 'owner']); 109 | 110 | $invitation->team->owner->setVisible(['name']); 111 | 112 | return $invitation; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/UserController.php: -------------------------------------------------------------------------------- 1 | users = $users; 28 | 29 | $this->middleware('auth'); 30 | } 31 | 32 | /** 33 | * Get the current user of the application. 34 | * 35 | * @return \Illuminate\Http\Response 36 | */ 37 | public function getCurrentUser() 38 | { 39 | return $this->users->getCurrentUser(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/PasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 31 | } 32 | 33 | /** 34 | * Display the form to request a password reset link. 35 | * 36 | * @return \Illuminate\Http\Response 37 | */ 38 | public function getEmail() 39 | { 40 | return view('auth.password.email'); 41 | } 42 | 43 | /** 44 | * Display the password reset view for the given token. 45 | * 46 | * @param string $token 47 | * @return \Illuminate\Http\Response 48 | */ 49 | public function getReset($token = null) 50 | { 51 | if (is_null($token)) { 52 | throw new NotFoundHttpException; 53 | } 54 | 55 | return view('auth.password.reset')->with('token', $token); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 17 | } 18 | 19 | /** 20 | * Show the terms of service for the application. 21 | * 22 | * @return \Illuminate\Http\Response 23 | */ 24 | public function show() 25 | { 26 | return view('home'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Controllers/Settings/DashboardController.php: -------------------------------------------------------------------------------- 1 | users = $users; 30 | 31 | $this->middleware('auth'); 32 | } 33 | 34 | /** 35 | * Show the settings dashboard. 36 | * 37 | * @param \Illuminate\Http\Request $request 38 | * @return \Illuminate\Http\Response 39 | */ 40 | public function show(Request $request) 41 | { 42 | $data = [ 43 | 'activeTab' => $request->get('tab', Spark::firstSettingsTabKey()), 44 | 'invoices' => [], 45 | 'user' => $this->users->getCurrentUser(), 46 | ]; 47 | 48 | if (Auth::user()->stripe_id) { 49 | $data['invoices'] = Cache::remember('spark:invoices:'.Auth::id(), 30, function () { 50 | return Auth::user()->invoices(); 51 | }); 52 | } 53 | 54 | return view('settings.dashboard', $data); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Http/Controllers/Settings/ProfileController.php: -------------------------------------------------------------------------------- 1 | users = $users; 36 | 37 | $this->middleware('auth'); 38 | } 39 | 40 | /** 41 | * Update the user's profile information. 42 | * 43 | * @param \Illuminate\Http\Request $request 44 | * @return \Illuminate\Http\Response 45 | */ 46 | public function updateUserProfile(Request $request) 47 | { 48 | $this->validateUserProfile($request); 49 | 50 | $originalEmail = Auth::user()->email; 51 | 52 | if (Spark::$updateProfilesWith) { 53 | $this->callCustomUpdater(Spark::$updateProfilesWith, $request); 54 | } else { 55 | Auth::user()->fill($request->all())->save(); 56 | } 57 | 58 | if (Auth::user()->stripe_id && $originalEmail !== Auth::user()->email) { 59 | $this->updateStripeEmailAddress(); 60 | } 61 | 62 | event(new ProfileUpdated(Auth::user())); 63 | 64 | return $this->users->getCurrentUser(); 65 | } 66 | 67 | /** 68 | * Validate the incoming request to update the user's profile. 69 | * 70 | * @param \Illuminate\Http\Request $request 71 | * @return void 72 | */ 73 | protected function validateUserProfile(Request $request) 74 | { 75 | if (Spark::$validateProfileUpdatesWith) { 76 | $this->callCustomValidator( 77 | Spark::$validateProfileUpdatesWith, $request 78 | ); 79 | } else { 80 | $this->validate($request, [ 81 | 'name' => 'required|max:255', 82 | 'email' => 'required|email|unique:users,email,'.Auth::id(), 83 | ]); 84 | } 85 | } 86 | 87 | /** 88 | * Update the user's e-mail address on Stripe. 89 | * 90 | * @return void 91 | */ 92 | protected function updateStripeEmailAddress() 93 | { 94 | $customer = Auth::user()->asStripeCustomer(); 95 | 96 | $customer->email = Auth::user()->email; 97 | 98 | $customer->save(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/Http/Controllers/Stripe/WebhookController.php: -------------------------------------------------------------------------------- 1 | where( 24 | 'stripe_id', $payload['data']['object']['customer'] 25 | )->first(); 26 | 27 | if (is_null($user)) { 28 | return; 29 | } 30 | 31 | app(InvoiceNotifier::class)->notify( 32 | $user, $user->findInvoice($payload['data']['object']['id']) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Http/Controllers/TermsController.php: -------------------------------------------------------------------------------- 1 | text(file_get_contents(base_path('terms.md'))); 18 | 19 | return view('terms', compact('terms')); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Controllers/WelcomeController.php: -------------------------------------------------------------------------------- 1 | [ 27 | \App\Http\Middleware\EncryptCookies::class, 28 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 29 | \Illuminate\Session\Middleware\StartSession::class, 30 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 31 | \App\Http\Middleware\VerifyCsrfToken::class, 32 | ], 33 | 34 | 'api' => [ 35 | 'throttle:60,1', 36 | ], 37 | ]; 38 | 39 | /** 40 | * The application's route middleware. 41 | * 42 | * These middleware may be assigned to groups or used individually. 43 | * 44 | * @var array 45 | */ 46 | protected $routeMiddleware = [ 47 | 'auth' => \App\Http\Middleware\Authenticate::class, 48 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 49 | 'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class, 50 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 51 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 52 | ]; 53 | } 54 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 26 | } 27 | 28 | /** 29 | * Handle an incoming request. 30 | * 31 | * @param \Illuminate\Http\Request $request 32 | * @param \Closure $next 33 | * @return mixed 34 | */ 35 | public function handle($request, Closure $next) 36 | { 37 | if ($this->auth->guest()) { 38 | if ($request->ajax()) { 39 | return response('Unauthorized.', 401); 40 | } else { 41 | return redirect()->guest('/login'); 42 | } 43 | } 44 | 45 | return $next($request); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | getCustomValidator($callback, $request, $arguments); 23 | } else { 24 | $validator = $callback; 25 | } 26 | 27 | if ($validator->fails()) { 28 | $this->throwValidationException($request, $validator); 29 | } 30 | } 31 | 32 | /** 33 | * Get the custom validator based on the given callback. 34 | * 35 | * @param callable|string $callback 36 | * @param \Illuminate\Http\Request $request 37 | * @param array $arguments 38 | * @return \Illuminate\ContractsValidation\Validator 39 | */ 40 | protected function getCustomValidator($callback, Request $request, array $arguments = []) 41 | { 42 | if (is_string($callback)) { 43 | list($class, $method) = explode('@', $callback); 44 | 45 | $callback = [app($class), $method]; 46 | } 47 | 48 | $validator = call_user_func_array($callback, array_merge([$request], $arguments)); 49 | 50 | return $validator instanceof ValidatorContract 51 | ? $validator 52 | : Validator::make($request->all(), $validator); 53 | } 54 | 55 | /** 56 | * Call a custom Spark updater callback. 57 | * 58 | * @param callable|string $callback 59 | * @param \Illuminate\Http\Request $request 60 | * @param array $arguments 61 | * @return mixed 62 | */ 63 | public function callCustomUpdater($callback, Request $request, array $arguments = []) 64 | { 65 | if (is_string($callback)) { 66 | list($class, $method) = explode('@', $callback); 67 | 68 | $callback = [app($class), $method]; 69 | } 70 | 71 | return call_user_func_array($callback, array_merge([$request], $arguments)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/Jobs/Job.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any application authentication / authorization services. 21 | * 22 | * @param \Illuminate\Contracts\Auth\Access\Gate $gate 23 | * @return void 24 | */ 25 | public function boot(GateContract $gate) 26 | { 27 | $this->registerPolicies($gate); 28 | 29 | // 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'App\Listeners\EventListener', 18 | ], 19 | ]; 20 | 21 | /** 22 | * Register any other events for your application. 23 | * 24 | * @param \Illuminate\Contracts\Events\Dispatcher $events 25 | * @return void 26 | */ 27 | public function boot(DispatcherContract $events) 28 | { 29 | parent::boot($events); 30 | 31 | // 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapWebRoutes($router); 41 | 42 | // 43 | } 44 | 45 | /** 46 | * Define the "web" routes for the application. 47 | * 48 | * These routes all receive session state, CSRF protection, etc. 49 | * 50 | * @param \Illuminate\Routing\Router $router 51 | * @return void 52 | */ 53 | protected function mapWebRoutes(Router $router) 54 | { 55 | $router->group([ 56 | 'namespace' => $this->namespace, 'middleware' => 'web', 57 | ], function ($router) { 58 | require app_path('Http/routes.php'); 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/Providers/SparkServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->booted(function () { 28 | $this->defineRoutes(); 29 | }); 30 | } 31 | 32 | /** 33 | * Define the Spark routes. 34 | * 35 | * @return void 36 | */ 37 | protected function defineRoutes() 38 | { 39 | if (! $this->app->routesAreCached()) { 40 | $router = app('router'); 41 | 42 | $router->group(['namespace' => 'App\Http\Controllers'], function ($router) { 43 | require __DIR__.'/../Http/routes.php'; 44 | }); 45 | } 46 | } 47 | 48 | /** 49 | * Register any application services. 50 | * 51 | * @return void 52 | */ 53 | public function register() 54 | { 55 | $this->defineServices(); 56 | } 57 | 58 | /** 59 | * Bind the Spark services into the container. 60 | * 61 | * @return void 62 | */ 63 | protected function defineServices() 64 | { 65 | $services = [ 66 | RegistrarContract::class => Registrar::class, 67 | InvoiceNotifier::class => EmailInvoiceNotifier::class, 68 | UserRepositoryContract::class => UserRepository::class, 69 | TeamRepositoryContract::class => TeamRepository::class, 70 | ]; 71 | 72 | foreach ($services as $key => $value) { 73 | $this->app->bindIf($key, $value); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/Repositories/TeamRepository.php: -------------------------------------------------------------------------------- 1 | $data['name']]); 23 | 24 | $team->owner_id = $user->id; 25 | 26 | $team->save(); 27 | 28 | $team = $user->teams()->attach( 29 | $team, ['role' => 'owner'] 30 | ); 31 | 32 | return $team; 33 | } 34 | 35 | /** 36 | * Get the team for the given ID. 37 | * 38 | * @param \Illuminate\Contracts\Auth\Authenticatable $user 39 | * @param string $teamId 40 | * @return \Illuminate\Database\Eloquent\Model 41 | */ 42 | public function getTeam($user, $teamId) 43 | { 44 | return $user->teams()->with('users', 'invitations')->findOrFail($teamId); 45 | } 46 | 47 | /** 48 | * Get all of the teams for the user. 49 | * 50 | * @param \Illuminate\Contracts\Auth\Authenticatable $user 51 | * @return \Illuminate\Database\Eloquent\Collection 52 | */ 53 | public function getAllTeamsForUser($user) 54 | { 55 | $teams = $user->teams()->with('owner')->get(); 56 | 57 | foreach ($teams as $team) { 58 | $team->owner->setVisible(['name']); 59 | } 60 | 61 | return $teams; 62 | } 63 | 64 | /** 65 | * Get all of the pending invitations for the user. 66 | * 67 | * @param \Illuminate\Contracts\Auth\Authenticatable $user 68 | * @return \Illuminate\Database\Eloquent\Collection 69 | */ 70 | public function getPendingInvitationsForUser($user) 71 | { 72 | $invitations = $user->invitations()->with('team.owner')->get(); 73 | 74 | foreach ($invitations as $invite) { 75 | $invite->setVisible(['id', 'team']); 76 | 77 | $invite->team->setVisible(['name', 'owner']); 78 | 79 | $invite->team->owner->setVisible(['name']); 80 | } 81 | 82 | return $invitations; 83 | } 84 | 85 | /** 86 | * Attach a user to a given team based on their invitation. 87 | * 88 | * @param string $invitationId 89 | * @param \Illuminate\Contracts\Auth\Authenticatable $user 90 | * @return void 91 | */ 92 | public function attachUserToTeamByInvitation($invitationId, $user) 93 | { 94 | $userModel = get_class($user); 95 | 96 | $inviteModel = get_class((new $userModel)->invitations()->getQuery()->getModel()); 97 | 98 | $invitation = (new $inviteModel)->where('token', $invitationId)->first(); 99 | 100 | if ($invitation) { 101 | $user->joinTeamById($invitation->team->id); 102 | 103 | $user->switchToTeam($invitation->team); 104 | 105 | $invitation->delete(); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/Services/Auth/TwoFactor/Authy.php: -------------------------------------------------------------------------------- 1 | getTwoFactorAuthProviderOptions()['id']); 21 | } 22 | 23 | /** 24 | * Register the given user with the provider. 25 | * 26 | * @param \App\Contracts\Auth\TwoFactor\Authenticatable $user 27 | * @return void 28 | */ 29 | public function register(TwoFactorAuthenticatable $user) 30 | { 31 | $key = config('services.authy.key'); 32 | 33 | $response = json_decode((new HttpClient)->post('https://api.authy.com/protected/json/users/new?api_key='.$key, [ 34 | 'form_params' => [ 35 | 'user' => [ 36 | 'email' => $user->getEmailForTwoFactorAuth(), 37 | 'cellphone' => preg_replace('/[^0-9]/', '', $user->getAuthPhoneNumber()), 38 | 'country_code' => $user->getAuthCountryCode(), 39 | ], 40 | ], 41 | ])->getBody(), true); 42 | 43 | $user->setTwoFactorAuthProviderOptions([ 44 | 'id' => $response['user']['id'], 45 | ]); 46 | } 47 | 48 | /** 49 | * Determine if the given token is valid for the given user. 50 | * 51 | * @param \App\Contracts\Auth\TwoFactor\Authenticatable $user 52 | * @param string $token 53 | * @return bool 54 | */ 55 | public function tokenIsValid(TwoFactorAuthenticatable $user, $token) 56 | { 57 | try { 58 | $key = config('services.authy.key'); 59 | 60 | $options = $user->getTwoFactorAuthProviderOptions(); 61 | 62 | $response = json_decode((new HttpClient)->get( 63 | 'https://api.authy.com/protected/json/verify/'.$token.'/'.$options['id'].'?force=true&api_key='.$key 64 | )->getBody(), true); 65 | 66 | return $response['token'] === 'is valid'; 67 | } catch (Exception $e) { 68 | return false; 69 | } 70 | } 71 | 72 | /** 73 | * Delete the given user from the provider. 74 | * 75 | * @param \App\Contracts\Auth\TwoFactor\Authenticatable $user 76 | * @return bool 77 | */ 78 | public function delete(TwoFactorAuthenticatable $user) 79 | { 80 | $key = config('services.authy.key'); 81 | 82 | $options = $user->getTwoFactorAuthProviderOptions(); 83 | 84 | (new HttpClient)->post( 85 | 'https://api.authy.com/protected/json/users/delete/'.$options['id'].'?api_key='.$key 86 | ); 87 | 88 | $user->setTwoFactorAuthProviderOptions([]); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/Team.php: -------------------------------------------------------------------------------- 1 | belongsTo(Spark::model('teams', Team::class), 'team_id'); 38 | } 39 | 40 | /** 41 | * Determine if the coupon is expired. 42 | * 43 | * @return bool 44 | */ 45 | public function isExpired() 46 | { 47 | return Carbon::now()->subWeek()->gte($this->created_at); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/Teams/Team.php: -------------------------------------------------------------------------------- 1 | belongsToMany( 32 | config('auth.providers.users.model'), 'user_teams', 'team_id', 'user_id' 33 | )->withPivot('role'); 34 | } 35 | 36 | /** 37 | * Get the owner of the team. 38 | */ 39 | public function owner() 40 | { 41 | return $this->belongsTo(config('auth.providers.users.model'), 'owner_id'); 42 | } 43 | 44 | /** 45 | * Get all of the pending invitations for the team. 46 | */ 47 | public function invitations() 48 | { 49 | return $this->hasMany(Invitation::class)->orderBy('created_at', 'desc'); 50 | } 51 | 52 | /** 53 | * Invite a user to the team by e-mail address. 54 | * 55 | * @param string $email 56 | * @return \App\Teams\Invitation 57 | */ 58 | public function inviteUserByEmail($email) 59 | { 60 | $model = config('auth.providers.users.model'); 61 | 62 | $invitedUser = (new $model)->where('email', $email)->first(); 63 | 64 | $invitation = $this->invitations() 65 | ->where('email', $email)->first(); 66 | 67 | if (! $invitation) { 68 | $invitation = $this->invitations()->create([ 69 | 'user_id' => $invitedUser ? $invitedUser->id : null, 70 | 'email' => $email, 71 | 'token' => str_random(40), 72 | ]); 73 | } 74 | 75 | $email = $invitation->user_id 76 | ? 'emails.team.invitations.existing' 77 | : 'emails.team.invitations.new'; 78 | 79 | Mail::send($email, compact('invitation'), function ($m) use ($invitation) { 80 | $m->to($invitation->email)->subject('New Invitation!'); 81 | }); 82 | 83 | return $invitation; 84 | } 85 | 86 | /** 87 | * Remove a user from the team by their ID. 88 | * 89 | * @param int $userId 90 | * @return void 91 | */ 92 | public function removeUserById($userId) 93 | { 94 | $this->users()->detach([$userId]); 95 | 96 | $userModel = config('auth.providers.users.model'); 97 | 98 | $removedUser = (new $userModel)->find($userId); 99 | 100 | if ($removedUser) { 101 | $removedUser->refreshCurrentTeam(); 102 | } 103 | 104 | event(new RemovedFromTeam($removedUser, $this)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/User.php: -------------------------------------------------------------------------------- 1 | paid()) > 0 || $force; 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/Ux/Settings/Tab.php: -------------------------------------------------------------------------------- 1 | name = $name; 54 | $this->view = $view; 55 | $this->icon = $icon; 56 | $this->displayable = $displayable; 57 | $this->key = str_slug($this->name); 58 | } 59 | 60 | /** 61 | * Determine if the tab should be displayed. 62 | * 63 | * @return bool 64 | */ 65 | public function displayable() 66 | { 67 | if ($this->displayable) { 68 | return call_user_func_array($this->displayable, func_get_args()); 69 | } 70 | 71 | return true; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/Ux/Settings/Tabs.php: -------------------------------------------------------------------------------- 1 | tabs = $tabs; 25 | } 26 | 27 | /** 28 | * Define the settings tabs configuration. 29 | * 30 | * @param callable $callback 31 | * @return $this 32 | */ 33 | public function configure(callable $callback) 34 | { 35 | $this->tabs = array_filter(call_user_func($callback, $this)); 36 | 37 | return $this; 38 | } 39 | 40 | /** 41 | * Create a new custom tab instance. 42 | * 43 | * @param string $name 44 | * @param string $view 45 | * @param string $icon 46 | * @return \App\Ux\Settings\Tab 47 | */ 48 | public function make($name, $view, $icon, callable $displayable = null) 49 | { 50 | return new Tab($name, $view, $icon, $displayable); 51 | } 52 | 53 | /** 54 | * Get an array of the displayable tabs. 55 | * 56 | * @param dynamic 57 | * @return array 58 | */ 59 | public function displayable() 60 | { 61 | $arguments = func_get_args(); 62 | 63 | return array_values(array_filter($this->tabs, function ($tab) use ($arguments) { 64 | return call_user_func_array([$tab, 'displayable'], $arguments); 65 | })); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/Ux/Settings/TeamTabs.php: -------------------------------------------------------------------------------- 1 | ownsTeam($team); 16 | }); 17 | } 18 | 19 | /** 20 | * Get the tab configuration for the "Membership" tab. 21 | * 22 | * @return \App\Ux\Settings\Tab 23 | */ 24 | public function membership() 25 | { 26 | return new Tab('Membership', 'settings.team.tabs.membership', 'fa-users'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 32 | 33 | $status = $kernel->handle( 34 | $input = new Symfony\Component\Console\Input\ArgvInput, 35 | new Symfony\Component\Console\Output\ConsoleOutput 36 | ); 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Shutdown The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once Artisan has finished running. We will fire off the shutdown events 44 | | so that any final work may be done by the application before we shut 45 | | down the process. This is the last thing to happen to the request. 46 | | 47 | */ 48 | 49 | $kernel->terminate($input, $status); 50 | 51 | exit($status); 52 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /bootstrap/autoload.php: -------------------------------------------------------------------------------- 1 | =5.5.9", 9 | "laravel/framework": "5.2.*", 10 | "laravel/cashier": "~6.0", 11 | "erusev/parsedown": "~1.0", 12 | "guzzlehttp/guzzle": "~6.0", 13 | "dompdf/dompdf": "^0.6.2" 14 | }, 15 | "require-dev": { 16 | "fzaninotto/faker": "~1.4", 17 | "mockery/mockery": "0.9.*", 18 | "phpunit/phpunit": "~4.0", 19 | "symfony/css-selector": "2.8.*|3.0.*", 20 | "symfony/dom-crawler": "2.8.*|3.0.*" 21 | }, 22 | "autoload": { 23 | "classmap": [ 24 | "database" 25 | ], 26 | "psr-4": { 27 | "App\\": "app/" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "classmap": [ 32 | "tests/TestCase.php" 33 | ] 34 | }, 35 | "scripts": { 36 | "post-root-package-install": [ 37 | "php -r \"copy('.env.example', '.env');\"" 38 | ], 39 | "post-create-project-cmd": [ 40 | "php artisan key:generate" 41 | ], 42 | "post-install-cmd": [ 43 | "Illuminate\\Foundation\\ComposerScripts::postInstall", 44 | "php artisan optimize" 45 | ], 46 | "post-update-cmd": [ 47 | "Illuminate\\Foundation\\ComposerScripts::postUpdate", 48 | "php artisan optimize" 49 | ] 50 | }, 51 | "config": { 52 | "preferred-install": "dist" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'pusher'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Broadcast Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may define all of the broadcast connections that will be used 24 | | to broadcast events to other systems or over websockets. Samples of 25 | | each available type of connection are provided inside this array. 26 | | 27 | */ 28 | 29 | 'connections' => [ 30 | 31 | 'pusher' => [ 32 | 'driver' => 'pusher', 33 | 'key' => env('PUSHER_KEY'), 34 | 'secret' => env('PUSHER_SECRET'), 35 | 'app_id' => env('PUSHER_APP_ID'), 36 | 'options' => [ 37 | // 38 | ], 39 | ], 40 | 41 | 'redis' => [ 42 | 'driver' => 'redis', 43 | 'connection' => 'default', 44 | ], 45 | 46 | 'log' => [ 47 | 'driver' => 'log', 48 | ], 49 | 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Cache Stores 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may define all of the cache "stores" for your application as 24 | | well as their drivers. You may even define multiple stores for the 25 | | same cache driver to group types of items stored in your caches. 26 | | 27 | */ 28 | 29 | 'stores' => [ 30 | 31 | 'apc' => [ 32 | 'driver' => 'apc', 33 | ], 34 | 35 | 'array' => [ 36 | 'driver' => 'array', 37 | ], 38 | 39 | 'database' => [ 40 | 'driver' => 'database', 41 | 'table' => 'cache', 42 | 'connection' => null, 43 | ], 44 | 45 | 'file' => [ 46 | 'driver' => 'file', 47 | 'path' => storage_path('framework/cache'), 48 | ], 49 | 50 | 'memcached' => [ 51 | 'driver' => 'memcached', 52 | 'servers' => [ 53 | [ 54 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 55 | 'port' => env('MEMCACHED_PORT', 11211), 56 | 'weight' => 100, 57 | ], 58 | ], 59 | ], 60 | 61 | 'redis' => [ 62 | 'driver' => 'redis', 63 | 'connection' => 'default', 64 | ], 65 | 66 | ], 67 | 68 | /* 69 | |-------------------------------------------------------------------------- 70 | | Cache Key Prefix 71 | |-------------------------------------------------------------------------- 72 | | 73 | | When utilizing a RAM based store such as APC or Memcached, there might 74 | | be other applications utilizing the same cache. So, we'll specify a 75 | | value to get prefixed to all our keys so we can avoid collisions. 76 | | 77 | */ 78 | 79 | 'prefix' => 'laravel', 80 | 81 | ]; 82 | -------------------------------------------------------------------------------- /config/compile.php: -------------------------------------------------------------------------------- 1 | [ 17 | // 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled File Providers 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may list service providers which define a "compiles" function 26 | | that returns additional files that should be compiled, providing an 27 | | easy way to get common files from any packages you are utilizing. 28 | | 29 | */ 30 | 31 | 'providers' => [ 32 | // 33 | ], 34 | 35 | ]; 36 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | 'local', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Default Cloud Filesystem Disk 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Many applications store files both locally and in the cloud. For this 26 | | reason, you may specify a default "cloud" driver here. This driver 27 | | will be bound as the Cloud disk implementation in the container. 28 | | 29 | */ 30 | 31 | 'cloud' => 's3', 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Filesystem Disks 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Here you may configure as many filesystem "disks" as you wish, and you 39 | | may even configure multiple disks of the same driver. Defaults have 40 | | been setup for each driver as an example of the required options. 41 | | 42 | */ 43 | 44 | 'disks' => [ 45 | 46 | 'local' => [ 47 | 'driver' => 'local', 48 | 'root' => storage_path('app'), 49 | ], 50 | 51 | 'public' => [ 52 | 'driver' => 'local', 53 | 'root' => storage_path('app/public'), 54 | 'visibility' => 'public', 55 | ], 56 | 57 | 's3' => [ 58 | 'driver' => 's3', 59 | 'key' => 'your-key', 60 | 'secret' => 'your-secret', 61 | 'region' => 'your-region', 62 | 'bucket' => 'your-bucket', 63 | ], 64 | 65 | ], 66 | 67 | ]; 68 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_DRIVER', 'sync'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Queue Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may configure the connection information for each server that 26 | | is used by your application. A default configuration has been added 27 | | for each back-end shipped with Laravel. You are free to add more. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'expire' => 60, 42 | ], 43 | 44 | 'beanstalkd' => [ 45 | 'driver' => 'beanstalkd', 46 | 'host' => 'localhost', 47 | 'queue' => 'default', 48 | 'ttr' => 60, 49 | ], 50 | 51 | 'sqs' => [ 52 | 'driver' => 'sqs', 53 | 'key' => 'your-public-key', 54 | 'secret' => 'your-secret-key', 55 | 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id', 56 | 'queue' => 'your-queue-name', 57 | 'region' => 'us-east-1', 58 | ], 59 | 60 | 'redis' => [ 61 | 'driver' => 'redis', 62 | 'connection' => 'default', 63 | 'queue' => 'default', 64 | 'expire' => 60, 65 | ], 66 | 67 | ], 68 | 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Failed Queue Jobs 72 | |-------------------------------------------------------------------------- 73 | | 74 | | These options configure the behavior of failed queue job logging so you 75 | | can control which database and table are used to store the jobs that 76 | | have failed. You may change them to any database / table you wish. 77 | | 78 | */ 79 | 80 | 'failed' => [ 81 | 'database' => env('DB_CONNECTION', 'mysql'), 82 | 'table' => 'failed_jobs', 83 | ], 84 | 85 | ]; 86 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | ], 21 | 22 | 'ses' => [ 23 | 'key' => env('SES_KEY'), 24 | 'secret' => env('SES_SECRET'), 25 | 'region' => 'us-east-1', 26 | ], 27 | 28 | 'sparkpost' => [ 29 | 'secret' => env('SPARKPOST_SECRET'), 30 | ], 31 | 32 | 'stripe' => [ 33 | 'model' => App\User::class, 34 | 'key' => env('STRIPE_KEY'), 35 | 'secret' => env('STRIPE_SECRET'), 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | realpath(base_path('resources/views')), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => realpath(storage_path('framework/views')), 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /database/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | define(App\User::class, function (Faker\Generator $faker) { 15 | return [ 16 | 'name' => $faker->name, 17 | 'email' => $faker->safeEmail, 18 | 'password' => bcrypt(str_random(10)), 19 | 'remember_token' => str_random(10), 20 | ]; 21 | }); 22 | -------------------------------------------------------------------------------- /database/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('name'); 18 | $table->string('email')->unique(); 19 | $table->string('password', 60); 20 | $table->rememberToken(); 21 | 22 | // Two-Factor Authentication Columns... 23 | $table->string('phone_country_code')->nullable(); 24 | $table->string('phone_number')->nullable(); 25 | $table->text('two_factor_options')->nullable(); 26 | 27 | // Team Columns... 28 | $table->integer('current_team_id')->nullable(); 29 | 30 | // Cashier Columns... 31 | $table->string('stripe_id')->nullable(); 32 | $table->string('card_brand', 25)->nullable(); 33 | $table->string('card_last_four', 4)->nullable(); 34 | $table->text('extra_billing_info')->nullable(); 35 | 36 | $table->timestamps(); 37 | }); 38 | } 39 | 40 | /** 41 | * Reverse the migrations. 42 | * 43 | * @return void 44 | */ 45 | public function down() 46 | { 47 | Schema::drop('users'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 17 | $table->string('token')->index(); 18 | $table->timestamp('created_at'); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::drop('password_resets'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_subscriptions_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->integer('user_id')->index(); 18 | $table->string('name'); 19 | $table->string('stripe_id'); 20 | $table->string('stripe_plan'); 21 | $table->integer('quantity'); 22 | $table->timestamp('trial_ends_at')->nullable(); 23 | $table->timestamp('ends_at')->nullable(); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::drop('subscriptions'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_200000_create_teams_tables.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('owner_id')->index()->nullable(); 19 | $table->string('name'); 20 | $table->timestamps(); 21 | }); 22 | 23 | // Create User Teams Intermediate Table... 24 | Schema::create('user_teams', function (Blueprint $table) { 25 | $table->integer('team_id'); 26 | $table->integer('user_id'); 27 | $table->string('role', 25); 28 | 29 | $table->unique(['team_id', 'user_id']); 30 | }); 31 | 32 | // Create Invitations Table... 33 | Schema::create('invitations', function (Blueprint $table) { 34 | $table->increments('id'); 35 | $table->integer('team_id')->index(); 36 | $table->integer('user_id')->nullable()->index(); 37 | $table->string('email'); 38 | $table->string('token', 40)->unique(); 39 | $table->timestamps(); 40 | }); 41 | } 42 | 43 | /** 44 | * Reverse the migrations. 45 | * 46 | * @return void 47 | */ 48 | public function down() 49 | { 50 | Schema::drop('teams'); 51 | Schema::drop('user_teams'); 52 | Schema::drop('invitations'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /database/seeds/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(UsersTableSeeder::class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var elixir = require('laravel-elixir'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Elixir Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Elixir provides a clean, fluent API for defining some basic Gulp tasks 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for our application, as well as publishing vendor resources. 11 | | 12 | */ 13 | 14 | elixir(function(mix) { 15 | mix.sass('app.scss') 16 | .browserify('app.js') 17 | .version(['css/app.css', 'js/app.js']); 18 | }); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "gulp": "^3.9.1" 5 | }, 6 | "dependencies": { 7 | "laravel-elixir": "^5.0.0", 8 | "jquery": "^2.1.4", 9 | "bootstrap-sass": "^3.0.0", 10 | "moment": "^2.10.6", 11 | "promise": "^7.0.4", 12 | "underscore": "^1.8.3", 13 | "vue": "^1.0.0", 14 | "vue-resource": "^0.1.11" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | 18 | ./app 19 | 20 | ./app/Http/routes.php 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Redirect Trailing Slashes If Not A Folder... 9 | RewriteCond %{REQUEST_FILENAME} !-d 10 | RewriteRule ^(.*)/$ /$1 [L,R=301] 11 | 12 | # Handle Front Controller... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_FILENAME} !-f 15 | RewriteRule ^ index.php [L] 16 | 17 | # Handle Authorization Header 18 | RewriteCond %{HTTP:Authorization} . 19 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 20 | 21 | -------------------------------------------------------------------------------- /public/build/rev-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "css/app.css": "css/app-d5481a85bb.css", 3 | "js/app.js": "js/app-4377a2b0f7.js" 4 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkgrep/laravel-spark/4007b02bf110d0cfc2585e9b0249bd65f24cde08/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /* 11 | |-------------------------------------------------------------------------- 12 | | Register The Auto Loader 13 | |-------------------------------------------------------------------------- 14 | | 15 | | Composer provides a convenient, automatically generated class loader for 16 | | our application. We just need to utilize it! We'll simply require it 17 | | into the script here so that we don't have to worry about manual 18 | | loading any of our classes later on. It feels nice to relax. 19 | | 20 | */ 21 | 22 | require __DIR__.'/../bootstrap/autoload.php'; 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Turn On The Lights 27 | |-------------------------------------------------------------------------- 28 | | 29 | | We need to illuminate PHP development, so let us turn on the lights. 30 | | This bootstraps the framework and gets it ready for use, then it 31 | | will load up this application so that we can run it and send 32 | | the responses back to the browser and delight our users. 33 | | 34 | */ 35 | 36 | $app = require_once __DIR__.'/../bootstrap/app.php'; 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Run The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once we have the application, we can handle the incoming request 44 | | through the kernel, and send the associated response back to 45 | | the client's browser allowing them to enjoy the creative 46 | | and wonderful application we have prepared for them. 47 | | 48 | */ 49 | 50 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 51 | 52 | $response = $kernel->handle( 53 | $request = Illuminate\Http\Request::capture() 54 | ); 55 | 56 | $response->send(); 57 | 58 | $kernel->terminate($request, $response); 59 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Spark-based Laravel installation 2 | 3 | Since `laravel/spark` is not free anymore and cannot be installed or updated with `composer`, you can use this basic 4 | installation of Laravel 5.2 with all Spark code integrated in it. 5 | 6 | ## Releases 7 | 8 | The version numbering follows [Laravel releases](https://github.com/laravel/laravel/releases). 9 | 10 | ## License 11 | 12 | The Laravel framework is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). 13 | -------------------------------------------------------------------------------- /resources/assets/js/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /resources/assets/js/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Laravel Spark - Creating Amazing Experiences. 4 | |-------------------------------------------------------------------------- 5 | | 6 | | First, we will load all of the "core" dependencies for Spark which are 7 | | libraries such as Vue and jQuery. This also loads the Spark helpers 8 | | for things such as HTTP calls, forms, and form validation errors. 9 | | 10 | | Next, we will create the root Vue application for Spark. This'll start 11 | | the entire application and attach it to the DOM. Of course, you may 12 | | customize this script as you desire and load your own components. 13 | | 14 | */ 15 | 16 | require('./core/bootstrap'); 17 | 18 | new Vue(require('./spark')); 19 | -------------------------------------------------------------------------------- /resources/assets/js/auth/registration/simple.js: -------------------------------------------------------------------------------- 1 | Vue.component('spark-simple-registration-screen', { 2 | /* 3 | * Bootstrap the component. Load the initial data. 4 | */ 5 | ready: function () { 6 | $(function () { 7 | $('.spark-first-field').filter(':visible:first').focus(); 8 | }); 9 | 10 | var queryString = URI(document.URL).query(true); 11 | 12 | /** 13 | * Load the query string items such as the invitation. 14 | */ 15 | if (queryString.invitation) { 16 | this.getInvitation(queryString.invitation); 17 | } 18 | }, 19 | 20 | /* 21 | * Initial state of the component's data. 22 | */ 23 | data: function () { 24 | return { 25 | invitation: null, 26 | failedToLoadInvitation: false, 27 | 28 | forms: { 29 | registration: $.extend(true, new SparkForm({ 30 | team_name: '', 31 | name: '', 32 | email: '', 33 | password: '', 34 | password_confirmation: '', 35 | plan: '', 36 | terms: false, 37 | invitation: null 38 | }), Spark.forms.registration) 39 | } 40 | }; 41 | }, 42 | 43 | 44 | methods: { 45 | /** 46 | * Get the specified invitation. 47 | */ 48 | getInvitation: function (invitation) { 49 | this.$http.get('/spark/api/teams/invitation/' + invitation) 50 | .success(function (invitation) { 51 | this.invitation = invitation; 52 | this.forms.registration.invitation = invitation.token; 53 | 54 | setTimeout(function () { 55 | $(function () { 56 | $('.spark-first-field').filter(':visible:first').focus(); 57 | }); 58 | }, 250); 59 | }) 60 | .error(function (errors) { 61 | this.failedToLoadInvitation = true; 62 | 63 | console.error('Unable to load invitation for given code.'); 64 | }); 65 | }, 66 | 67 | 68 | /* 69 | * Initialize the registration process. 70 | */ 71 | register: function () { 72 | Spark.post('/register', this.forms.registration) 73 | .then(function (response) { 74 | window.location = response.path; 75 | }); 76 | } 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /resources/assets/js/common/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Common Error Display Component. 3 | */ 4 | Vue.component('spark-errors', { 5 | props: ['form'], 6 | 7 | template: "
\ 8 | Whoops! There were some problems with your input.

\ 9 | \ 14 |
" 15 | }); 16 | 17 | 18 | Vue.component('spark-error-alert', { 19 | props: ['form'], 20 | 21 | template: "
\ 22 | Whoops! There were some problems with your input.\ 23 |
" 24 | }); 25 | -------------------------------------------------------------------------------- /resources/assets/js/core/bootstrap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Load Vue & Vue-Resource. 3 | * 4 | * Vue is the JavaScript framework used by Spark. 5 | */ 6 | if (window.Vue === undefined) { 7 | window.Vue = require('vue'); 8 | } 9 | 10 | require('vue-resource'); 11 | 12 | Vue.http.headers.common['X-CSRF-TOKEN'] = Spark.csrfToken; 13 | 14 | /** 15 | * Load Promises library. 16 | */ 17 | window.Promise = require('promise'); 18 | 19 | /* 20 | * Load Underscore.js, used for map / reduce on arrays. 21 | */ 22 | if (window._ === undefined) { 23 | window._ = require('underscore'); 24 | } 25 | 26 | /* 27 | * Load Moment.js, used for date formatting and presentation. 28 | */ 29 | if (window.moment === undefined) { 30 | window.moment = require('moment'); 31 | } 32 | 33 | /* 34 | * Load jQuery and Bootstrap jQuery, used for front-end interaction. 35 | */ 36 | if (window.$ === undefined || window.jQuery === undefined) { 37 | window.$ = window.jQuery = require('jquery'); 38 | } 39 | 40 | require('bootstrap-sass/assets/javascripts/bootstrap'); 41 | 42 | /** 43 | * Define the Spark component extension points. 44 | */ 45 | Spark.components = { 46 | profileBasics: {}, 47 | teamOwnerBasics: {}, 48 | editTeamMember: {}, 49 | navDropdown: {} 50 | }; 51 | 52 | /** 53 | * Load the Spark form utilities. 54 | */ 55 | require('./../forms/bootstrap'); 56 | -------------------------------------------------------------------------------- /resources/assets/js/core/components.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Load all of the core Spark Vue components. 3 | */ 4 | require('./../common/errors'); 5 | require('./../nav'); 6 | require('./../auth/registration/simple'); 7 | require('./../auth/registration/subscription'); 8 | require('./../settings/dashboard'); 9 | require('./../settings/dashboard/profile/basics') 10 | require('./../settings/dashboard/subscription'); 11 | require('./../settings/dashboard/security/password') 12 | require('./../settings/dashboard/security/two-factor') 13 | require('./../settings/dashboard/teams'); 14 | require('./../settings/team'); 15 | require('./../settings/team/owner/basics') 16 | require('./../settings/team/membership'); 17 | require('./../settings/team/membership/edit-team-member') 18 | -------------------------------------------------------------------------------- /resources/assets/js/forms/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Initialize the Spark form extension points. 3 | */ 4 | Spark.forms = { 5 | registration: {}, 6 | updateProfileBasics: {}, 7 | updateTeamOwnerBasics: {} 8 | }; 9 | 10 | /** 11 | * Load the SparkForm helper class. 12 | */ 13 | require('./instance'); 14 | 15 | /** 16 | * Define the form error collection class. 17 | */ 18 | require('./errors'); 19 | 20 | /** 21 | * Add additional form helpers to the Spark object. 22 | */ 23 | $.extend(Spark, require('./http')); 24 | 25 | /** 26 | * Define the Spark form input components. 27 | */ 28 | require('./components'); 29 | -------------------------------------------------------------------------------- /resources/assets/js/forms/components.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Text field input component for Bootstrap. 3 | */ 4 | Vue.component('spark-text', { 5 | props: ['display', 'form', 'name', 'input'], 6 | 7 | template: '
\ 8 | \ 9 |
\ 10 | \ 11 | \ 12 | {{ form.errors.get(name) }}\ 13 | \ 14 |
\ 15 |
' 16 | }); 17 | 18 | 19 | /** 20 | * E-mail field input component for Bootstrap. 21 | */ 22 | Vue.component('spark-email', { 23 | props: ['display', 'form', 'name', 'input'], 24 | 25 | template: '
\ 26 | \ 27 |
\ 28 | \ 29 | \ 30 | {{ form.errors.get(name) }}\ 31 | \ 32 |
\ 33 |
' 34 | }); 35 | 36 | 37 | /** 38 | * Password field input component for Bootstrap. 39 | */ 40 | Vue.component('spark-password', { 41 | props: ['display', 'form', 'name', 'input'], 42 | 43 | template: '
\ 44 | \ 45 |
\ 46 | \ 47 | \ 48 | {{ form.errors.get(name) }}\ 49 | \ 50 |
\ 51 |
' 52 | }); 53 | 54 | 55 | /** 56 | * Select input component for Bootstrap. 57 | */ 58 | Vue.component('spark-select', { 59 | props: ['display', 'form', 'name', 'items', 'input'], 60 | 61 | template: '
\ 62 | \ 63 |
\ 64 | \ 69 | \ 70 | {{ form.errors.get(name) }}\ 71 | \ 72 |
\ 73 |
' 74 | }); 75 | -------------------------------------------------------------------------------- /resources/assets/js/forms/errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Spark form error collection class. 3 | */ 4 | window.SparkFormErrors = function () { 5 | this.errors = {}; 6 | 7 | /** 8 | * Determine if the collection has any errors. 9 | */ 10 | this.hasErrors = function () { 11 | return !_.isEmpty(this.errors); 12 | }; 13 | 14 | 15 | /** 16 | * Determine if the collection has errors for a given field. 17 | */ 18 | this.has = function (field) { 19 | return _.indexOf(_.keys(this.errors), field) > -1; 20 | }; 21 | 22 | 23 | /** 24 | * Get all of the raw errors for the collection. 25 | */ 26 | this.all = function () { 27 | return this.errors; 28 | }; 29 | 30 | 31 | /** 32 | * Get all of the errors for the collection in a flat array. 33 | */ 34 | this.flatten = function () { 35 | return _.flatten(_.toArray(this.errors)); 36 | }; 37 | 38 | 39 | /** 40 | * Get the first error message for a given field. 41 | */ 42 | this.get = function (field) { 43 | if (this.has(field)) { 44 | return this.errors[field][0]; 45 | } 46 | }; 47 | 48 | 49 | /** 50 | * Set the raw errors for the collection. 51 | */ 52 | this.set = function (errors) { 53 | if (typeof errors === 'object') { 54 | this.errors = errors; 55 | } else { 56 | this.errors = {'field': ['Something went wrong. Please try again.']}; 57 | } 58 | }; 59 | 60 | 61 | /** 62 | * Forget all of the errors currently in the collection. 63 | */ 64 | this.forget = function () { 65 | this.errors = {}; 66 | }; 67 | }; 68 | -------------------------------------------------------------------------------- /resources/assets/js/forms/http.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * A few helper methods for making HTTP requests and doing common form actions. 4 | */ 5 | post: function (uri, form) { 6 | return Spark.sendForm('post', uri, form); 7 | }, 8 | 9 | 10 | put: function (uri, form) { 11 | return Spark.sendForm('put', uri, form); 12 | }, 13 | 14 | 15 | delete: function (uri, form) { 16 | return Spark.sendForm('delete', uri, form); 17 | }, 18 | 19 | 20 | /** 21 | * Send the form to the back-end server. Perform common form tasks. 22 | * 23 | * This function will automatically clear old errors, update "busy" status, etc. 24 | */ 25 | sendForm: function (method, uri, form) { 26 | return new Promise(function (resolve, reject) { 27 | form.startProcessing(); 28 | 29 | Vue.http[method](uri, form) 30 | .success(function (response) { 31 | form.finishProcessing(); 32 | 33 | resolve(response); 34 | }) 35 | .error(function (errors) { 36 | form.errors.set(errors); 37 | form.busy = false; 38 | 39 | reject(errors); 40 | }); 41 | }); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /resources/assets/js/forms/instance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SparkForm helper class. Used to set common properties on all forms. 3 | */ 4 | window.SparkForm = function (data) { 5 | var form = this; 6 | 7 | $.extend(this, data); 8 | 9 | this.errors = new SparkFormErrors(); 10 | this.busy = false; 11 | this.successful = false; 12 | 13 | this.startProcessing = function () { 14 | form.errors.forget(); 15 | form.busy = true; 16 | form.successful = false; 17 | }; 18 | 19 | this.finishProcessing = function () { 20 | form.busy = false; 21 | form.successful = true; 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /resources/assets/js/nav.js: -------------------------------------------------------------------------------- 1 | Vue.component('spark-nav-bar-dropdown', $.extend(true, { 2 | /* 3 | * Initial state of the component's data. 4 | */ 5 | data: function () { 6 | return { 7 | user: null, 8 | teams: [] 9 | }; 10 | }, 11 | 12 | 13 | events: { 14 | /** 15 | * Handle the "userRetrieved" event. 16 | */ 17 | userRetrieved: function (user) { 18 | this.user = user; 19 | 20 | return true; 21 | }, 22 | 23 | 24 | /** 25 | * Handle the "teamsRetrieved" event. 26 | */ 27 | teamsRetrieved: function (teams) { 28 | this.teams = teams; 29 | 30 | return true; 31 | } 32 | } 33 | }, Spark.components.navDropdown)); 34 | -------------------------------------------------------------------------------- /resources/assets/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel-spark", 3 | "version": "1.1.3", 4 | "description": "Laravel Spark", 5 | "main": "spark.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/laravel/spark" 9 | }, 10 | "keywords": [ 11 | "laravel", 12 | "spark" 13 | ], 14 | "author": { 15 | "name": "Taylor Otwell" 16 | }, 17 | "license": "MIT", 18 | "homepage": "https://github.com/laravel/spark", 19 | "dependencies": { 20 | "jquery": "^2.1.4", 21 | "moment": "^2.10.6", 22 | "promise": "^7.0.4", 23 | "underscore": "^1.8.3", 24 | "vue": "^1.0.0", 25 | "vue-resource": "^0.1.11" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /resources/assets/js/settings/dashboard.js: -------------------------------------------------------------------------------- 1 | Vue.component('spark-settings-screen', { 2 | /* 3 | * Bootstrap the component. Load the initial data. 4 | */ 5 | ready: function () { 6 | // 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /resources/assets/js/settings/dashboard/profile/basics.js: -------------------------------------------------------------------------------- 1 | Vue.component('spark-settings-profile-basics-screen', $.extend(true, { 2 | /* 3 | * Bootstrap the component. Load the initial data. 4 | */ 5 | ready: function () { 6 | // 7 | }, 8 | 9 | 10 | /* 11 | * Initial state of the component's data. 12 | */ 13 | data: function () { 14 | return { 15 | user: null, 16 | 17 | forms: { 18 | updateProfileBasics: $.extend(true, new SparkForm({ 19 | name: '', 20 | email: '', 21 | }), Spark.forms.updateProfileBasics) 22 | } 23 | }; 24 | }, 25 | 26 | 27 | events: { 28 | /* 29 | * Handle the "userRetrieved" event. 30 | */ 31 | userRetrieved: function (user) { 32 | this.user = user; 33 | 34 | this.updateProfileBasicsFormForNewUser(user); 35 | 36 | return true; 37 | } 38 | }, 39 | 40 | 41 | methods: { 42 | /** 43 | * Update the user profile form with new user information. 44 | */ 45 | updateProfileBasicsFormForNewUser: function (user) { 46 | this.forms.updateProfileBasics.name = user.name; 47 | this.forms.updateProfileBasics.email = user.email; 48 | }, 49 | 50 | 51 | /** 52 | * Update the user's profile information. 53 | */ 54 | updateProfileBasics: function () { 55 | var self = this; 56 | 57 | Spark.put('/settings/user', this.forms.updateProfileBasics) 58 | .then(function () { 59 | self.$dispatch('updateUser'); 60 | }); 61 | } 62 | } 63 | }, Spark.components.profileBasics)); 64 | -------------------------------------------------------------------------------- /resources/assets/js/settings/dashboard/security/password.js: -------------------------------------------------------------------------------- 1 | Vue.component('spark-settings-security-password-screen', { 2 | /* 3 | * Bootstrap the component. Load the initial data. 4 | */ 5 | ready: function () { 6 | // 7 | }, 8 | 9 | 10 | /* 11 | * Initial state of the component's data. 12 | */ 13 | data: function () { 14 | return { 15 | user: null, 16 | 17 | forms: { 18 | updatePassword: new SparkForm({ 19 | current_password: '', 20 | password: '', 21 | password_confirmation: '' 22 | }) 23 | } 24 | }; 25 | }, 26 | 27 | 28 | events: { 29 | /* 30 | * Handle the "userRetrieved" event. 31 | */ 32 | userRetrieved: function (user) { 33 | this.user = user; 34 | 35 | return true; 36 | } 37 | }, 38 | 39 | 40 | methods: { 41 | /** 42 | * Update the user's password. 43 | */ 44 | updatePassword: function () { 45 | var self = this; 46 | 47 | Spark.put('/settings/user/password', this.forms.updatePassword) 48 | .then(function () { 49 | self.forms.updatePassword.current_password = ''; 50 | self.forms.updatePassword.password = ''; 51 | self.forms.updatePassword.password_confirmation = ''; 52 | }); 53 | } 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /resources/assets/js/settings/dashboard/security/two-factor.js: -------------------------------------------------------------------------------- 1 | Vue.component('spark-settings-security-two-factor-screen', { 2 | /* 3 | * Bootstrap the component. Load the initial data. 4 | */ 5 | ready: function () { 6 | // 7 | }, 8 | 9 | 10 | /* 11 | * Initial state of the component's data. 12 | */ 13 | data: function () { 14 | return { 15 | user: null, 16 | 17 | forms: { 18 | enableTwoFactorAuth: new SparkForm({ 19 | country_code: '', 20 | phone_number: '', 21 | enabled: false 22 | }), 23 | 24 | disableTwoFactorAuth: new SparkForm({}) 25 | } 26 | }; 27 | }, 28 | 29 | 30 | events: { 31 | /* 32 | * Handle the "userRetrieved" event. 33 | */ 34 | userRetrieved: function (user) { 35 | this.user = user; 36 | 37 | this.updateEnableTwoFactorAuthFormForNewUser(user); 38 | 39 | return true; 40 | } 41 | }, 42 | 43 | 44 | methods: { 45 | /** 46 | * Update the user profile form with new user information. 47 | */ 48 | updateEnableTwoFactorAuthFormForNewUser: function (user) { 49 | this.forms.enableTwoFactorAuth.country_code = user.phone_country_code; 50 | this.forms.enableTwoFactorAuth.phone_number = user.phone_number; 51 | }, 52 | 53 | 54 | /** 55 | * Enable two-factor authentication for the user. 56 | */ 57 | enableTwoFactorAuth: function () { 58 | var self = this; 59 | 60 | Spark.post('/settings/user/two-factor', this.forms.enableTwoFactorAuth) 61 | .then(function () { 62 | self.$dispatch('updateUser'); 63 | 64 | self.forms.enableTwoFactorAuth.enabled = true; 65 | }); 66 | }, 67 | 68 | 69 | /** 70 | * Disable two-factor authentication for the user. 71 | */ 72 | disableTwoFactorAuth: function () { 73 | var self = this; 74 | 75 | this.forms.enableTwoFactorAuth.enabled = false; 76 | 77 | Spark.delete('/settings/user/two-factor', this.forms.disableTwoFactorAuth) 78 | .then(function () { 79 | self.$dispatch('updateUser'); 80 | }); 81 | } 82 | } 83 | }); 84 | -------------------------------------------------------------------------------- /resources/assets/js/settings/team.js: -------------------------------------------------------------------------------- 1 | Vue.component('spark-team-settings-screen', { 2 | props: ['teamId'], 3 | 4 | /* 5 | * Bootstrap the component. Load the initial data. 6 | */ 7 | ready: function () { 8 | this.getTeam(); 9 | this.getRoles(); 10 | }, 11 | 12 | 13 | /* 14 | * Initial state of the component's data. 15 | */ 16 | data: function () { 17 | return {team: null}; 18 | }, 19 | 20 | 21 | events: { 22 | /* 23 | * Handle the "updateTeam" event. Re-retrieve the team. 24 | */ 25 | updateTeam: function () { 26 | this.getTeam(); 27 | 28 | return true; 29 | } 30 | }, 31 | 32 | 33 | methods: { 34 | /* 35 | * Get the team from the API. 36 | */ 37 | getTeam: function () { 38 | this.$http.get('/spark/api/teams/' + this.teamId) 39 | .success(function (team) { 40 | this.team = team; 41 | 42 | this.$broadcast('teamRetrieved', team); 43 | }); 44 | }, 45 | 46 | 47 | /** 48 | * Get all of the roles that may be assigned to users. 49 | */ 50 | getRoles: function () { 51 | this.$http.get('/spark/api/teams/roles') 52 | .success(function (roles) { 53 | this.roles = roles; 54 | 55 | this.$broadcast('rolesRetrieved', roles); 56 | }); 57 | } 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /resources/assets/js/settings/team/membership/edit-team-member.js: -------------------------------------------------------------------------------- 1 | Vue.component('spark-team-settings-edit-team-member-screen', $.extend(true, { 2 | props: ['teamMember'], 3 | 4 | /* 5 | * Initial state of the component's data. 6 | */ 7 | data: function () { 8 | return { 9 | user: null, 10 | team: null, 11 | roles: [], 12 | 13 | forms: { 14 | updateTeamMember: new SparkForm({ 15 | role: '' 16 | }) 17 | } 18 | }; 19 | }, 20 | 21 | 22 | watch: { 23 | /** 24 | * Watch for updates to the "teamMember" data. 25 | */ 26 | 'teamMember': function (teamMember) { 27 | this.forms.updateTeamMember.role = teamMember.pivot.role; 28 | } 29 | }, 30 | 31 | 32 | computed: { 33 | /** 34 | * Get the roles that may be assigned to users. 35 | */ 36 | assignableRoles: function () { 37 | return _.reject(this.roles, function (role) { 38 | return role.value == 'owner'; 39 | }); 40 | }, 41 | }, 42 | 43 | 44 | events: { 45 | /* 46 | * Handle the "userRetrieved" event. 47 | */ 48 | userRetrieved: function (user) { 49 | this.user = user; 50 | 51 | return true; 52 | }, 53 | 54 | 55 | /* 56 | * Handle the "teamRetrieved" event. 57 | */ 58 | teamRetrieved: function (team) { 59 | this.team = team; 60 | 61 | return true; 62 | }, 63 | 64 | 65 | /* 66 | * Handle the "rolesRetrieved" event. 67 | */ 68 | rolesRetrieved: function (roles) { 69 | this.roles = roles; 70 | 71 | return true; 72 | } 73 | }, 74 | 75 | 76 | methods: { 77 | /* 78 | * Edit a given team member. 79 | */ 80 | updateTeamMember: function () { 81 | var self = this; 82 | 83 | Spark.put('/settings/teams/' + this.team.id + '/members/' + this.teamMember.id, this.forms.updateTeamMember) 84 | .then(function () { 85 | self.$dispatch('updateTeam'); 86 | 87 | $('#modal-edit-team-member').modal('hide'); 88 | }); 89 | } 90 | } 91 | }, Spark.components.editTeamMember)); 92 | -------------------------------------------------------------------------------- /resources/assets/js/settings/team/owner/basics.js: -------------------------------------------------------------------------------- 1 | Vue.component('spark-team-settings-owner-basics-screen', $.extend(true, { 2 | /* 3 | * Bootstrap the component. Load the initial data. 4 | */ 5 | ready: function () { 6 | // 7 | }, 8 | 9 | 10 | data: function () { 11 | return { 12 | user: null, 13 | team: null, 14 | 15 | forms: { 16 | updateTeamOwnerBasics: $.extend(true, new SparkForm({ 17 | name: '' 18 | }), Spark.forms.updateTeamOwnerBasics) 19 | } 20 | }; 21 | }, 22 | 23 | 24 | events: { 25 | /* 26 | * Handle the "userRetrieved" event. 27 | */ 28 | userRetrieved: function (user) { 29 | this.user = user; 30 | 31 | return true; 32 | }, 33 | 34 | 35 | /* 36 | * Handle the "teamRetrieved" event. 37 | */ 38 | teamRetrieved: function (team) { 39 | this.team = team; 40 | 41 | this.forms.updateTeamOwnerBasics.name = this.team.name; 42 | 43 | return true; 44 | } 45 | }, 46 | 47 | 48 | methods: { 49 | /** 50 | * Update the team's information. 51 | */ 52 | updateTeam: function () { 53 | var self = this; 54 | 55 | Spark.put('/settings/teams/' + this.team.id, this.forms.updateTeamOwnerBasics) 56 | .then(function () { 57 | self.$dispatch('updateTeam'); 58 | self.$dispatch('updateTeams'); 59 | }); 60 | } 61 | } 62 | }, Spark.components.teamOwnerBasics)); 63 | -------------------------------------------------------------------------------- /resources/assets/js/spark.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Load the Spark components. 3 | */ 4 | require('./core/components'); 5 | 6 | /** 7 | * Export the Spark application. 8 | */ 9 | module.exports = { 10 | el: '#spark-app', 11 | 12 | /* 13 | * Bootstrap the application. Load the initial data. 14 | */ 15 | ready: function () { 16 | $(function () { 17 | $('.spark-first-field').filter(':visible:first').focus(); 18 | }); 19 | 20 | if (Spark.userId) { 21 | this.getUser(); 22 | } 23 | 24 | if (Spark.currentTeamId) { 25 | this.getTeams(); 26 | this.getCurrentTeam(); 27 | } 28 | 29 | this.whenReady(); 30 | }, 31 | 32 | 33 | events: { 34 | /** 35 | * Handle requests to update the current user from a child component. 36 | */ 37 | updateUser: function () { 38 | this.getUser(); 39 | 40 | return true; 41 | }, 42 | 43 | 44 | /** 45 | * Handle requests to update the teams from a child component. 46 | */ 47 | updateTeams: function () { 48 | this.getTeams(); 49 | 50 | return true; 51 | } 52 | }, 53 | 54 | 55 | methods: { 56 | /** 57 | * This method would be overridden by developer. 58 | */ 59 | whenReady: function () { 60 | // 61 | }, 62 | 63 | 64 | /** 65 | * Retrieve the user from the API and broadcast it to children. 66 | */ 67 | getUser: function () { 68 | this.$http.get('/spark/api/users/me') 69 | .success(function (user) { 70 | this.$broadcast('userRetrieved', user); 71 | }); 72 | }, 73 | 74 | /* 75 | * Get all of the user's current teams from the API. 76 | */ 77 | getTeams: function () { 78 | this.$http.get('/spark/api/teams') 79 | .success(function (teams) { 80 | this.$broadcast('teamsRetrieved', teams); 81 | }); 82 | }, 83 | 84 | 85 | /* 86 | * Get the user's current team from the API. 87 | */ 88 | getCurrentTeam: function () { 89 | this.$http.get('/spark/api/teams/' + Spark.currentTeamId) 90 | .success(function (team) { 91 | this.$broadcast('currentTeamRetrieved', team); 92 | }); 93 | } 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /resources/assets/sass/app.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Spark Variables... 3 | */ 4 | @import "themes/spark/variables"; 5 | 6 | /* 7 | * Bootstrap CSS... 8 | */ 9 | @import "../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap"; 10 | 11 | /** 12 | * Spark Essentials... 13 | */ 14 | @import "essentials"; 15 | 16 | /* 17 | * Spark Styling... 18 | */ 19 | @import "themes/spark/spark"; 20 | -------------------------------------------------------------------------------- /resources/assets/sass/essentials.scss: -------------------------------------------------------------------------------- 1 | html { 2 | min-height: 100%; 3 | position: relative; 4 | } 5 | 6 | body { 7 | margin-bottom: 80px; 8 | } 9 | 10 | [v-cloak] { 11 | display: none; 12 | } 13 | 14 | .fa-btn { 15 | margin-right: 5px; 16 | } 17 | 18 | .footer { 19 | background-color: white; 20 | border-top: 1px solid #e7e7e7; 21 | bottom: 0; 22 | min-height: 80px; 23 | position: absolute; 24 | width: 100%; 25 | } 26 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/components/settings.scss: -------------------------------------------------------------------------------- 1 | .spark-screen { 2 | .spark-settings-tabs-stacked { 3 | border-radius: $border-radius-base; 4 | 5 | a { 6 | border-bottom: 1px solid rgba(0, 0, 0, .1); 7 | border-left: 3px solid transparent; 8 | color: #555; 9 | 10 | i { 11 | color: #aaa; 12 | position: relative; 13 | } 14 | } 15 | 16 | li:last-child a { 17 | border-bottom: 0; 18 | } 19 | 20 | li.active a { 21 | border-left: 3px solid $brand-primary; 22 | } 23 | 24 | li a:active, li a:hover, li a:link, li a:visited { 25 | background-color: white; 26 | } 27 | } 28 | 29 | .spark-plan-change-selector-heading { 30 | font-size: $font-size-base; 31 | margin-bottom: 15px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/components/terms.scss: -------------------------------------------------------------------------------- 1 | #spark-terms-screen { 2 | h1 { 3 | font-size: 26px; 4 | margin-top: 35px; 5 | margin-bottom: 20px; 6 | 7 | &:first-child { 8 | margin-top: 10px; 9 | } 10 | } 11 | 12 | h2 { 13 | font-size: 20px; 14 | } 15 | 16 | h3 { 17 | font-size: 18px; 18 | } 19 | 20 | h4 { 21 | font-size: 16px; 22 | margin-top: 20px; 23 | } 24 | 25 | p { 26 | line-height: 25px; 27 | margin: 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/elements/alerts.scss: -------------------------------------------------------------------------------- 1 | .spark-screen { 2 | .alert { 3 | font-weight: bold; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/elements/buttons.scss: -------------------------------------------------------------------------------- 1 | .spark-screen { 2 | .btn { 3 | font-size: 12px; 4 | font-weight: 500; 5 | text-transform: uppercase; 6 | transition: background-color 0.2s ease; 7 | } 8 | 9 | .btn-link { 10 | font-weight: 300; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/elements/forms.scss: -------------------------------------------------------------------------------- 1 | .spark-screen { 2 | label { 3 | font-weight: 300; 4 | } 5 | 6 | .has-error { 7 | @include form-control-validation($state-danger-bg, $state-danger-bg, $state-danger-bg); 8 | 9 | .control-label { 10 | color: $text-color; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/elements/modals.scss: -------------------------------------------------------------------------------- 1 | .modal-title { 2 | font-weight: 300; 3 | } 4 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/elements/nav.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | background-color: white; 3 | font-size: 16px; 4 | font-weight: 300; 5 | margin-bottom: 40px; 6 | 7 | .navbar-brand { 8 | font-size: 28px; 9 | 10 | i { 11 | color: $brand-primary; 12 | } 13 | } 14 | 15 | .navbar-nav { 16 | padding-top: 0px; 17 | 18 | > li > a { 19 | font-size: 14px; 20 | font-weight: 400; 21 | text-transform: uppercase; 22 | } 23 | } 24 | 25 | .dropdown-menu { 26 | li { 27 | font-size: 14px; 28 | 29 | a { 30 | font-weight: 300; 31 | line-height: 25px; 32 | vertical-align: middle; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/elements/panels.scss: -------------------------------------------------------------------------------- 1 | .spark-screen { 2 | .panel-body { 3 | font-weight: 300; 4 | } 5 | 6 | .panel-heading { 7 | border-bottom: 0; 8 | font-size: 18px; 9 | font-weight: 500; 10 | text-transform: uppercase; 11 | } 12 | 13 | .panel-flush { 14 | .panel-body, .panel-header { 15 | padding: 0; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/elements/tables.scss: -------------------------------------------------------------------------------- 1 | .spark-screen { 2 | td.spark-table-pad { 3 | padding-top: 14px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/elements/tooltips.scss: -------------------------------------------------------------------------------- 1 | .spark-screen { 2 | .tooltip-inner { 3 | max-width: 500px; 4 | width: 300px; 5 | } 6 | 7 | .tooltip-inner ul { 8 | padding-top: 10px; 9 | padding-right: 25px; 10 | padding-left: 25px; 11 | } 12 | 13 | .tooltip-inner li { 14 | font-size: 14px; 15 | line-height: 25px; 16 | text-align: left; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/spark.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Elements 3 | */ 4 | @import "elements/alerts"; 5 | @import "elements/buttons"; 6 | @import "elements/forms"; 7 | @import "elements/modals"; 8 | @import "elements/nav"; 9 | @import "elements/panels"; 10 | @import "elements/tables"; 11 | @import "elements/tooltips"; 12 | /** 13 | * Components 14 | */ 15 | @import "components/splash"; 16 | @import "components/subscription"; 17 | @import "components/settings"; 18 | @import "components/terms"; 19 | 20 | /* 21 | * Misc. 22 | */ 23 | 24 | body { 25 | font-weight: 300; 26 | } 27 | 28 | #spark-layout { 29 | background-color: #f5f5f5; 30 | } 31 | 32 | .text-success { 33 | color: $state-success-bg; 34 | } 35 | -------------------------------------------------------------------------------- /resources/assets/sass/themes/spark/variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Variables... 3 | */ 4 | $brand-primary: #5291cb; 5 | $brand-success: #adce42; 6 | $brand-danger: #fe7f65; 7 | 8 | $state-success-text: #ffffff; 9 | $state-success-bg: #adce42; 10 | $state-success-border: #adce42; 11 | 12 | $state-info-text: #ffffff; 13 | $state-info-bg: #5291cb; 14 | $state-info-border: #5291cb; 15 | 16 | $state-danger-text: #ffffff; 17 | $state-danger-bg: #fe7f65; 18 | $state-danger-border: #fe7f65; 19 | 20 | $text-color: #4c5557; 21 | $font-family-sans-serif: "Lato", Helvetica, Arial, sans-serif !default; 22 | $font-size-base: 14px; 23 | 24 | $btn-font-weight: 300; 25 | 26 | $navbar-height: 65px; 27 | 28 | $panel-default-heading-bg: #ffffff; 29 | $panel-default-text: #969696; 30 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Passwords must be at least six characters and match the confirmation.', 17 | 'reset' => 'Your password has been reset!', 18 | 'sent' => 'We have e-mailed your password reset link!', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that e-mail address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | 4 | @section('content') 5 |
6 |
7 |
8 |
9 |
Login
10 |
11 | @include('common.error-alert', ['form' => 'default']) 12 | 13 |
14 | {!! csrf_field() !!} 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @if ($errors->has('email')) 23 | 24 | {{ $errors->first('email') }} 25 | 26 | @endif 27 |
28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 | @if ($errors->has('password')) 37 | 38 | {{ $errors->first('password') }} 39 | 40 | @endif 41 |
42 |
43 | 44 |
45 |
46 |
47 | 50 |
51 |
52 |
53 | 54 |
55 |
56 | 59 | 60 | Forgot Your Password? 61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | @endsection 70 | -------------------------------------------------------------------------------- /resources/views/auth/password/email.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | 4 | @section('content') 5 |
6 |
7 |
8 |
9 |
Reset Password
10 |
11 | @include('common.error-alert', ['form' => 'default']) 12 | 13 | @if (session('status')) 14 |
15 | {{ session('status') }} 16 |
17 | @endif 18 | 19 |
20 | {!! csrf_field() !!} 21 | 22 |
23 | 24 | 25 |
26 | 27 | 28 | @if ($errors->has('email')) 29 | 30 | {{ $errors->first('email') }} 31 | 32 | @endif 33 |
34 |
35 | 36 |
37 |
38 | 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | @endsection 50 | -------------------------------------------------------------------------------- /resources/views/auth/registration/simple.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | 4 | @section('scripts') 5 | 6 | @endsection 7 | 8 | 9 | @section('content') 10 | 11 |
12 | 13 |
14 |
15 | @include('auth.registration.subscription.invitation') 16 |
17 |
18 | 19 | 20 |
21 |
22 | @include('auth.registration.simple.basics') 23 |
24 |
25 |
26 |
27 | @endsection 28 | -------------------------------------------------------------------------------- /resources/views/auth/registration/simple/basics.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
Register
3 | 4 |
5 | 6 | 7 |
8 | @if (Spark::usingTeams()) 9 | 14 | 15 | @endif 16 | 17 | 21 | 22 | 23 | 27 | 28 | 29 | 33 | 34 | 35 | 39 | 40 | 41 |
42 |
43 |
44 | 53 |
54 |
55 |
56 | 57 |
58 |
59 | 69 |
70 |
71 |
72 |
73 |
74 | -------------------------------------------------------------------------------- /resources/views/auth/registration/subscription.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | 4 | @section('scripts') 5 | 6 | 7 | 8 | @endsection 9 | 10 | 11 | @section('content') 12 | 13 |
14 | 15 |
16 | @include('auth.registration.subscription.inspiration') 17 |
18 | 19 | 20 |
21 | @include('auth.registration.subscription.plans.selector') 22 |
23 | 24 | 25 |
26 | 27 |
28 | @include('auth.registration.subscription.plans.selected') 29 |
30 | 31 | 32 |
33 | @include('auth.registration.subscription.coupon') 34 |
35 | 36 | 37 |
38 | @include('auth.registration.subscription.invitation') 39 |
40 | 41 | 42 |
43 | @include('auth.registration.subscription.basics') 44 |
45 | 46 | 47 |
48 | @include('auth.registration.subscription.billing') 49 |
50 |
51 |
52 |
53 | @endsection 54 | -------------------------------------------------------------------------------- /resources/views/auth/registration/subscription/basics.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
The Basics
3 |
4 | 5 | 6 |
7 | @if (Spark::usingTeams()) 8 | 12 | 13 | @endif 14 | 15 | 19 | 20 | 21 | 25 | 26 | 27 | 31 | 32 | 33 | 37 | 38 | 39 |
40 |
41 |
42 |
43 | 52 |
53 |
54 |
55 | 56 |
57 |
58 | 68 |
69 |
70 |
71 |
72 |
73 |
74 | -------------------------------------------------------------------------------- /resources/views/auth/registration/subscription/coupon.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
Discount
3 | 4 |
5 |
6 | Your coupon 7 | awards @{{ currentCouponDisplayDiscount | lowercase }} @{{ getCouponDisplayDuration(selectedPlan) | lowercase }} 8 | ! 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /resources/views/auth/registration/subscription/inspiration.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | Which plan is for you? 5 |
6 |
7 | 8 | 9 |
10 | Thanks for coming on board. 11 |
12 | -------------------------------------------------------------------------------- /resources/views/auth/registration/subscription/invitation.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
Invitation From @{{ invitation.team.owner.name }}
4 | 5 |
6 |
7 | We found your invitation to the @{{ invitation.team.name }} team! 8 |
9 |
10 |
11 | 12 | 13 |
14 |
Invitation
15 | 16 |
17 |
18 | We couldn't find this invitation. It is either expired or invalid. 19 |
20 |
21 |
22 | 23 | -------------------------------------------------------------------------------- /resources/views/auth/registration/subscription/plans/plan.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
@{{ plan.name }}
4 |
5 | 8 | 9 |
10 | 11 | 12 |
13 |
14 |
15 | @{{ plan.currencySymbol }}@{{ plan.price }} 16 |
17 | 18 |
19 | @{{ plan.currencySymbol }}@{{ plan.price }} 20 |
21 |
22 | 23 |
24 | @{{ plan.currencySymbol }}@{{ plan.price }} 25 |
26 |
27 | 28 | 29 |
30 | @{{ plan.interval }} 31 |
32 | 33 | 34 |
35 |
36 | 37 |
38 |
39 | @{{ plan.currencySymbol }}@{{ getDiscountPlanPrice(plan.price) }} 40 |
41 | 42 |
43 | @{{ getCouponDisplayDuration(plan) }} 44 |
45 |
46 |
47 | 48 |
49 | 50 |
51 | 64 |
65 |
66 |
67 | -------------------------------------------------------------------------------- /resources/views/auth/registration/subscription/plans/selected.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
Your Plan
3 |
4 | 5 | 6 |
7 | You have selected the @{{ selectedPlan.name }} 8 | (@{{ selectedPlanPrice }} / @{{ selectedPlan.interval | capitalize}}) plan. 9 |
10 | 11 | 12 |
13 | 16 |
17 | 18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /resources/views/auth/registration/subscription/plans/selector.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | Monthly   6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 |   Yearly 17 | 18 |
19 |
20 | 21 | 22 |
23 |
24 | @include('auth.registration.subscription.plans.plan') 25 |
26 |
27 | 28 | 29 |
30 |
31 | @include('auth.registration.subscription.plans.plan') 32 |
33 |
34 | -------------------------------------------------------------------------------- /resources/views/auth/token.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | 4 | @section('content') 5 |
6 |
7 |
8 |
9 |
Two-Factor Authentication
10 |
11 | @include('common.error-alert', ['form' => 'default']) 12 | 13 |
14 | {!! csrf_field() !!} 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @if ($errors->has('token')) 23 | 24 | {{ $errors->first('token') }} 25 | 26 | @endif 27 |
28 |
29 | 30 |
31 |
32 | 35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | @endsection 44 | -------------------------------------------------------------------------------- /resources/views/common/error-alert.blade.php: -------------------------------------------------------------------------------- 1 | @if (count($errors->{$form}) > 0) 2 |
3 | Whoops! There were some problems with your input. 4 |
5 | @endif 6 | -------------------------------------------------------------------------------- /resources/views/common/footer.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 19 | -------------------------------------------------------------------------------- /resources/views/emails/auth/password/email.blade.php: -------------------------------------------------------------------------------- 1 | Hi {{ explode(' ', $user->name)[0] }}! 2 | 3 |

4 | 5 | Here is a link allowing you to reset your password: 6 | {{ url('password/reset/'.$token) }} 7 | 8 |

9 | 10 | If you did not request a link to reset your password, please let us know. 11 | 12 |

13 | 14 | Thanks! 15 | 16 |
17 | 18 | {{ Spark::product() }} 19 | -------------------------------------------------------------------------------- /resources/views/emails/billing/invoice.blade.php: -------------------------------------------------------------------------------- 1 | Hi {{ explode(' ', $user->name)[0] }}! 2 | 3 |

4 | 5 | Thanks for your continued support. We've attached a copy of this month's invoice for your review. 6 | Please let us know if you have any questions or concerns! 7 | 8 |

9 | 10 | Thanks! 11 | 12 |
13 | 14 | {{ $invoiceData['product'] }} 15 | 16 | -------------------------------------------------------------------------------- /resources/views/emails/team/invitations/existing.blade.php: -------------------------------------------------------------------------------- 1 | Hi! 2 | 3 |

4 | 5 | {{ $invitation->team->owner->name }} has invited you to join their team! 6 | 7 |

8 | 9 | Since you already have an account, you may accept the invitation from your 10 | account settings screen. 11 | 12 |

13 | 14 | See you soon! 15 | 16 |
17 | 18 | {{ Spark::company() }} 19 | -------------------------------------------------------------------------------- /resources/views/emails/team/invitations/new.blade.php: -------------------------------------------------------------------------------- 1 | Hi! 2 | 3 |

4 | 5 | {{ $invitation->team->owner->name }} has invited you to join their team! If you do not already have an account, 6 | you may click the following link to get started: 7 | 8 |

9 | 10 | {{ url('register?invitation='.$invitation->token) }} 11 | 12 |

13 | 14 | See you soon! 15 | 16 |
17 | 18 | {{ Spark::company() }} 19 | -------------------------------------------------------------------------------- /resources/views/errors/503.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Be right back. 5 | 6 | 7 | 8 | 39 | 40 | 41 |
42 |
43 |
Be right back.
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /resources/views/home.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | 5 |
6 | @if (Spark::usingTeams() && ! Auth::user()->hasTeams()) 7 | 8 | 9 |
10 |
11 |
12 |
You Need A Team!
13 | 14 |
15 | It looks like you haven't created a team! 16 | You can create one in your account settings. 17 |
18 |
19 |
20 |
21 | 22 | @else 23 | 24 | 25 |
26 |
27 |
28 |
Dashboard
29 | 30 |
31 | Your Application's Dashboard. 32 |
33 |
34 |
35 |
36 | 37 | @endif 38 |
39 | @endsection 40 | -------------------------------------------------------------------------------- /resources/views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Laravel 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @include('scripts.globals') 20 | 21 | 22 | @yield('scripts', '') 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 |
34 | 35 | @if (Auth::check()) 36 | @include('nav.authenticated') 37 | @else 38 | @include('nav.guest') 39 | @endif 40 | 41 | 42 | @yield('content') 43 | 44 | 45 | @include('common.footer') 46 | 47 | 48 | 49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /resources/views/nav/authenticated.blade.php: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /resources/views/nav/guest.blade.php: -------------------------------------------------------------------------------- 1 | 35 | -------------------------------------------------------------------------------- /resources/views/nav/settings.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 63 | 64 | -------------------------------------------------------------------------------- /resources/views/scripts/globals.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 20 | -------------------------------------------------------------------------------- /resources/views/settings/dashboard.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | 4 | @section('scripts') 5 | 6 | @append 7 | 8 | 9 | @section('content') 10 | 11 | 12 |
13 |
14 | 15 |
16 |
17 |
18 | Settings 19 |
20 | 21 |
22 |
23 | 32 |
33 |
34 |
35 |
36 | 37 | 38 |
39 |
40 | @foreach (Spark::settingsTabs()->displayable() as $tab) 41 |
42 | @include($tab->view) 43 |
44 | @endforeach 45 |
46 |
47 |
48 |
49 |
50 | @endsection 51 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/profile.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @include('settings.tabs.profile.basics') 4 |
5 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/profile/basics.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
The Basics
4 | 5 |
6 | 7 | 8 |
9 | Great! Your profile was successfully updated. 10 |
11 | 12 |
13 | 17 | 18 | 19 | 23 | 24 | 25 |
26 |
27 | 37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/security.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @include('settings.tabs.security.password') 4 | 5 | 6 | @include('settings.tabs.security.two-factor') 7 |
8 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/security/password.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
Update Password
4 | 5 |
6 | 7 | 8 |
9 | Great! Your password was successfully updated. 10 |
11 | 12 |
13 | 17 | 18 | 19 | 23 | 24 | 25 | 29 | 30 | 31 |
32 |
33 | 43 |
44 |
45 |
46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | 6 | @include('settings.tabs.subscription.coupon') 7 | 8 | 9 | @include('settings.tabs.subscription.subscribe') 10 | 11 | 12 | @include('settings.tabs.subscription.change') 13 | 14 | 15 | @include('settings.tabs.subscription.card') 16 | 17 | 18 | @include('settings.tabs.subscription.resume') 19 | 20 | 21 | @if (count($invoices) > 0) 22 | @include('settings.tabs.subscription.invoices.vat') 23 | 24 | @include('settings.tabs.subscription.invoices.history') 25 | @endif 26 | 27 | 28 | @include('settings.tabs.subscription.cancel') 29 |
30 | 31 | 32 | @include('settings.tabs.subscription.modals.change') 33 | 34 | 35 | @include('settings.tabs.subscription.modals.cancel') 36 |
37 |
38 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription/cancel.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
Cancel Subscription
3 | 4 |
5 | 8 |
9 |
10 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription/change.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
Current Subscription
3 | 4 |
5 |
6 | You are subscribed to the @{{ currentPlan.name }} 7 | (@{{ currentPlan.currencySymbol }}@{{ currentPlan.price }} / @{{ currentPlan.interval | capitalize}}) plan. 8 |
9 | 10 |
11 | 14 |
15 | 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription/coupon.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
Current Discount
4 | 5 |
6 | 7 |
8 | You receive a @{{ currentPlan.interval }} discount of @{{ currentCouponDisplayDiscount }}. 9 |
10 | 11 | 12 |
13 | Your @{{ currentPlan.interval }} discount of @{{ currentCouponDisplayDiscount }} expires 14 | on @{{ currentCouponDisplayExpiresOn }}. 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription/invoices/history.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | Invoice History 4 |
5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | @foreach ($invoices as $invoice) 18 | 19 | 22 | 25 | 30 | 31 | @endforeach 32 | 33 |
DateAmountReceipt
20 | {{ $invoice->date()->format('Y-m-d') }} 21 | 23 | {{ $invoice->total() }} 24 | 26 | 27 | Download 28 | 29 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription/invoices/vat.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | VAT / Extra Billing Information 4 |
5 | 6 |
7 |
8 | If you need to add specific contact or tax information to your receipts, like your full business name, 9 | VAT identification number, or address of record, add it here. We'll make sure it shows up on every receipt. 10 |
11 | 12 | 13 | 14 |
15 | Done! Your extra billing information has been updated. 16 |
17 | 18 |
19 |
20 | 21 | 22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 | 39 |
40 |
41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription/modals/cancel.blade.php: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription/modals/change.blade.php: -------------------------------------------------------------------------------- 1 | 61 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription/modals/change/plan.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 23 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription/resume.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
Resume Subscription
3 | 4 |
5 |
6 | Your subscription has been cancelled. The benefits of your subscription will continue until your current 7 | billing 8 | period ends on @{{ subscriptionEndsAt }}. You may resume your subscription at no 9 | extra cost until the end of the billing period. 10 |
11 | 12 | 13 | 14 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription/subscribe/plan.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 23 | -------------------------------------------------------------------------------- /resources/views/settings/tabs/subscription/subscribe/selector.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | Monthly Plans 7 | 8 | 9 | 10 | Available Plans 11 | 12 |
13 | 14 |
15 |
16 | @include('settings.tabs.subscription.subscribe.plan') 17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 | Yearly Plans 25 |
26 | 27 |
28 | @include('settings.tabs.subscription.subscribe.plan') 29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /resources/views/settings/team.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | 4 | @section('scripts') 5 | 6 | @append 7 | 8 | 9 | @section('content') 10 | 11 |
12 |
13 | 14 |
15 |
16 |
17 | Team Settings (@{{ team.name }}) 18 |
19 | 20 |
21 | Loading    22 |
23 | 24 |
25 |
26 | 41 |
42 |
43 |
44 |
45 | 46 | 47 |
48 |
49 | @foreach (Spark::teamSettingsTabs()->displayable($team, Auth::user()) as $tab) 50 |
51 | @include($tab->view) 52 |
53 | @endforeach 54 |
55 |
56 |
57 |
58 |
59 | @endsection 60 | -------------------------------------------------------------------------------- /resources/views/settings/team/tabs/membership/modals/edit-team-member.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 42 | 43 | -------------------------------------------------------------------------------- /resources/views/settings/team/tabs/owner.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @include('settings.team.tabs.owner.basics') 4 |
5 | -------------------------------------------------------------------------------- /resources/views/settings/team/tabs/owner/basics.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
The Basics
4 | 5 |
6 | 7 | 8 |
9 | Great! Your team was successfully updated. 10 |
11 | 12 |
13 | 17 | 18 | 19 |
20 |
21 | 31 |
32 |
33 |
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /resources/views/terms.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | 4 | @section('content') 5 |
6 |
7 |
8 |
9 |
10 | Terms Of Service 11 |
12 | 13 |
14 | {!! $terms !!} 15 |
16 |
17 |
18 |
19 |
20 | @endsection 21 | -------------------------------------------------------------------------------- /resources/views/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | schedule-* 4 | compiled.php 5 | services.json 6 | events.scanned.php 7 | routes.scanned.php 8 | down 9 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /terms.md: -------------------------------------------------------------------------------- 1 | This page is generated from the `terms.md` file in your project root. 2 | -------------------------------------------------------------------------------- /tests/ExampleTest.php: -------------------------------------------------------------------------------- 1 | visit('/') 17 | ->see('Laravel 5'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); 22 | 23 | return $app; 24 | } 25 | } 26 | --------------------------------------------------------------------------------