├── public
├── favicon.ico
├── robots.txt
├── images
│ ├── iceburg.png
│ ├── screenshot1.jpg
│ ├── screenshot2.jpg
│ ├── screenshot3.jpg
│ ├── screenshot4.jpg
│ ├── screenshot5.jpg
│ ├── iceburg_resized.png
│ ├── MySQL-Sample-Database-Schema.png
│ └── Sharktopus.vs.Whalewolf.2015.1080p.BluRay.x265-RARBG-[rarbg.to].torrent
├── dist
│ ├── assets
│ │ ├── MacBook Pro.png
│ │ ├── favicon.svg
│ │ ├── logos
│ │ │ ├── Cross.svg
│ │ │ ├── CaretRight.svg
│ │ │ ├── Menu.svg
│ │ │ ├── Lightning.svg
│ │ │ ├── Facebook.svg
│ │ │ ├── Heart.svg
│ │ │ ├── Star.svg
│ │ │ ├── CheckedBox.svg
│ │ │ ├── Youtube.svg
│ │ │ ├── Instagram.svg
│ │ │ └── Sun.svg
│ │ ├── Underline1.svg
│ │ ├── Highlight3.svg
│ │ ├── Highlight2.svg
│ │ ├── Highlight1.svg
│ │ └── Underline2.svg
│ └── script.js
├── MySQL-Sample-Database-Diagram.pdf
├── .htaccess
└── index.php
├── database
├── .gitignore
├── factories
│ ├── Iceburg
│ │ ├── FieldFactory.php
│ │ ├── FieldTypeFactory.php
│ │ ├── BlankDataRecordFactory.php
│ │ └── ModuleFactory.php
│ ├── TeamFactory.php
│ └── UserFactory.php
└── migrations
│ ├── 2023_02_11_182343_create_saas_table.php
│ ├── 2023_01_30_050504_create_crm_statuses_table.php
│ ├── 2023_02_20_224345_create_crm_themes_table.php
│ ├── 2014_10_12_100000_create_password_resets_table.php
│ ├── 2023_01_04_025540_create_crm_types_table.php
│ ├── 2015_05_21_100000_create_teams_table.php
│ ├── 2020_05_21_200000_create_team_user_table.php
│ ├── 2019_08_19_000000_create_failed_jobs_table.php
│ ├── 2020_05_21_300000_create_team_invitations_table.php
│ ├── 2023_01_02_050520_create_sessions_table.php
│ ├── 2023_02_04_052358_create_jobs_table.php
│ ├── 2019_05_03_000003_create_subscription_items_table.php
│ ├── 2019_05_03_000001_create_customer_columns.php
│ ├── 2023_01_25_160524_create_crm_plans_table.php
│ ├── 2019_12_14_000001_create_personal_access_tokens_table.php
│ ├── 2019_05_03_000002_create_subscriptions_table.php
│ ├── 2023_01_04_025708_create_crms_table.php
│ ├── 2023_01_31_212727_create_connections_table.php
│ ├── 2014_10_12_000000_create_users_table.php
│ └── 2014_10_12_200000_add_two_factor_columns_to_users_table.php
├── bootstrap
├── cache
│ └── .gitignore
└── app.php
├── storage
├── logs
│ └── .gitignore
├── app
│ ├── public
│ │ └── .gitignore
│ └── .gitignore
└── framework
│ ├── testing
│ └── .gitignore
│ ├── views
│ └── .gitignore
│ ├── cache
│ ├── data
│ │ └── .gitignore
│ └── .gitignore
│ ├── sessions
│ └── .gitignore
│ └── .gitignore
├── app
├── Seeder
│ └── Seed.php
├── Http
│ ├── Controllers
│ │ ├── RegisterController.php
│ │ ├── StripeController.php
│ │ ├── Controller.php
│ │ └── InvoiceController.php
│ └── Middleware
│ │ ├── EncryptCookies.php
│ │ ├── VerifyCsrfToken.php
│ │ ├── PreventRequestsDuringMaintenance.php
│ │ ├── TrustHosts.php
│ │ ├── TrimStrings.php
│ │ ├── Authenticate.php
│ │ ├── ValidateSignature.php
│ │ ├── TrustProxies.php
│ │ ├── Cors.php
│ │ ├── RedirectIfAuthenticated.php
│ │ └── HandleInertiaRequests.php
├── Models
│ ├── Iceburg
│ │ ├── User_Role.php
│ │ ├── FieldType.php
│ │ ├── DataletType.php
│ │ ├── ModuleConvertable.php
│ │ ├── SubpanelField.php
│ │ ├── Role.php
│ │ ├── RelationshipModule.php
│ │ ├── ModuleGroup.php
│ │ ├── User.php
│ │ ├── Setting.php
│ │ └── Permission.php
│ ├── saas.php
│ ├── CrmPlan.php
│ ├── CrmTheme.php
│ ├── CrmType.php
│ ├── Database.php
│ ├── Server.php
│ ├── Connection.php
│ ├── CrmStatus.php
│ ├── Membership.php
│ ├── TeamInvitation.php
│ ├── BillingPortal.php
│ ├── Crm.php
│ └── Team.php
├── Actions
│ ├── Jetstream
│ │ ├── DeleteTeam.php
│ │ ├── UpdateTeamName.php
│ │ ├── CreateTeam.php
│ │ ├── DeleteUser.php
│ │ └── RemoveTeamMember.php
│ └── Fortify
│ │ ├── PasswordValidationRules.php
│ │ ├── ResetUserPassword.php
│ │ ├── UpdateUserPassword.php
│ │ ├── CreateNewUser.php
│ │ └── UpdateUserProfileInformation.php
├── Providers
│ ├── BroadcastServiceProvider.php
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── EventServiceProvider.php
│ ├── RouteServiceProvider.php
│ └── FortifyServiceProvider.php
├── Listeners
│ ├── CreateNewCRM.php
│ └── StripeEventListener.php
├── Console
│ ├── Kernel.php
│ └── Commands
│ │ └── DeleteExpiredFreeDatabases.php
├── Events
│ └── NewCrm.php
├── Jobs
│ └── GenerateCrm.php
├── Exceptions
│ └── Handler.php
├── Mail
│ └── SupportMail.php
└── Policies
│ └── TeamPolicy.php
├── resources
├── js
│ ├── Pages
│ │ ├── Payment.vue
│ │ ├── Subscription.vue
│ │ ├── Billing.vue
│ │ ├── Teams
│ │ │ ├── Create.vue
│ │ │ └── Show.vue
│ │ ├── API
│ │ │ └── Index.vue
│ │ ├── Auth
│ │ │ ├── ForgotPassword.vue
│ │ │ └── ConfirmPassword.vue
│ │ └── Profile
│ │ │ └── Show.vue
│ ├── Components
│ │ ├── SectionBorder.vue
│ │ ├── InputError.vue
│ │ ├── InputLabel.vue
│ │ ├── AuthenticationCard.vue
│ │ ├── ActionMessage.vue
│ │ ├── ApplicationMark.vue
│ │ ├── SectionTitle.vue
│ │ ├── PrimaryButton.vue
│ │ ├── DangerButton.vue
│ │ ├── SecondaryButton.vue
│ │ ├── ActionSection.vue
│ │ ├── AuthenticationCardLogo.vue
│ │ ├── TextInput.vue
│ │ ├── DaysSince.vue
│ │ ├── NavLink.vue
│ │ ├── Checkbox.vue
│ │ ├── DropdownLink.vue
│ │ ├── ResponsiveNavLink.vue
│ │ ├── DialogModal.vue
│ │ ├── Alert.vue
│ │ ├── FormSection.vue
│ │ └── ConfirmationModal.vue
│ ├── app.js
│ └── bootstrap.js
├── css
│ └── app.css
├── markdown
│ ├── policy.md
│ └── terms.md
└── views
│ ├── vendor
│ └── jetstream
│ │ ├── components
│ │ ├── input-error.blade.php
│ │ ├── section-border.blade.php
│ │ ├── label.blade.php
│ │ ├── dropdown-link.blade.php
│ │ ├── checkbox.blade.php
│ │ ├── input.blade.php
│ │ ├── authentication-card.blade.php
│ │ ├── button.blade.php
│ │ ├── danger-button.blade.php
│ │ ├── section-title.blade.php
│ │ ├── validation-errors.blade.php
│ │ ├── secondary-button.blade.php
│ │ ├── application-mark.blade.php
│ │ ├── action-section.blade.php
│ │ ├── authentication-card-logo.blade.php
│ │ ├── dialog-modal.blade.php
│ │ ├── action-message.blade.php
│ │ ├── nav-link.blade.php
│ │ ├── responsive-nav-link.blade.php
│ │ ├── switchable-team.blade.php
│ │ ├── form-section.blade.php
│ │ ├── confirmation-modal.blade.php
│ │ ├── dropdown.blade.php
│ │ ├── confirms-password.blade.php
│ │ └── modal.blade.php
│ │ └── mail
│ │ └── team-invitation.blade.php
│ ├── emails
│ └── support.blade.php
│ └── app.blade.php
├── postcss.config.js
├── tests
├── TestCase.php
├── Unit
│ └── ExampleTest.php
├── Feature
│ ├── ExampleTest.php
│ ├── BrowserSessionsTest.php
│ ├── CreateTeamTest.php
│ ├── UpdateTeamNameTest.php
│ ├── ProfileInformationTest.php
│ ├── DeleteApiTokenTest.php
│ ├── CreateApiTokenTest.php
│ ├── LeaveTeamTest.php
│ ├── DeleteTeamTest.php
│ ├── AuthenticationTest.php
│ ├── RemoveTeamMemberTest.php
│ ├── DeleteAccountTest.php
│ ├── PasswordConfirmationTest.php
│ ├── ApiTokenPermissionsTest.php
│ ├── UpdateTeamMemberRoleTest.php
│ ├── RegistrationTest.php
│ ├── InviteTeamMemberTest.php
│ └── UpdatePasswordTest.php
└── CreatesApplication.php
├── .gitattributes
├── .gitignore
├── .editorconfig
├── vite.config.js
├── lang
└── en
│ ├── pagination.php
│ ├── auth.php
│ └── passwords.php
├── routes
├── channels.php
├── api.php
└── console.php
├── tailwind.config.js
├── package.json
├── config
├── cors.php
├── view.php
├── services.php
└── hashing.php
├── phpunit.xml
├── .env.example
├── artisan
└── README.md
/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite*
2 |
--------------------------------------------------------------------------------
/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !public/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/framework/testing/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/app/Seeder/Seed.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !data/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/resources/js/Pages/Subscription.vue:
--------------------------------------------------------------------------------
1 |
2 | BillingPortal
3 |
4 |
--------------------------------------------------------------------------------
/resources/css/app.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/public/images/iceburg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iceburgcrm/iceburgsaas/HEAD/public/images/iceburg.png
--------------------------------------------------------------------------------
/public/images/screenshot1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iceburgcrm/iceburgsaas/HEAD/public/images/screenshot1.jpg
--------------------------------------------------------------------------------
/public/images/screenshot2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iceburgcrm/iceburgsaas/HEAD/public/images/screenshot2.jpg
--------------------------------------------------------------------------------
/public/images/screenshot3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iceburgcrm/iceburgsaas/HEAD/public/images/screenshot3.jpg
--------------------------------------------------------------------------------
/public/images/screenshot4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iceburgcrm/iceburgsaas/HEAD/public/images/screenshot4.jpg
--------------------------------------------------------------------------------
/public/images/screenshot5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iceburgcrm/iceburgsaas/HEAD/public/images/screenshot5.jpg
--------------------------------------------------------------------------------
/public/dist/assets/MacBook Pro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iceburgcrm/iceburgsaas/HEAD/public/dist/assets/MacBook Pro.png
--------------------------------------------------------------------------------
/public/images/iceburg_resized.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iceburgcrm/iceburgsaas/HEAD/public/images/iceburg_resized.png
--------------------------------------------------------------------------------
/resources/markdown/policy.md:
--------------------------------------------------------------------------------
1 | # Privacy Policy
2 |
3 | Edit this file to define the privacy policy for your application.
4 |
--------------------------------------------------------------------------------
/resources/markdown/terms.md:
--------------------------------------------------------------------------------
1 | # Terms of Service
2 |
3 | Edit this file to define the terms of service for your application.
4 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/MySQL-Sample-Database-Diagram.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iceburgcrm/iceburgsaas/HEAD/public/MySQL-Sample-Database-Diagram.pdf
--------------------------------------------------------------------------------
/public/images/MySQL-Sample-Database-Schema.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iceburgcrm/iceburgsaas/HEAD/public/images/MySQL-Sample-Database-Schema.png
--------------------------------------------------------------------------------
/app/Http/Controllers/RegisterController.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/input-error.blade.php:
--------------------------------------------------------------------------------
1 | @props(['for'])
2 |
3 | @error($for)
4 |
merge(['class' => 'text-sm text-red-600']) }}>{{ $message }}
5 | @enderror
6 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/section-border.blade.php:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/label.blade.php:
--------------------------------------------------------------------------------
1 | @props(['value'])
2 |
3 | merge(['class' => 'block font-medium text-sm text-gray-700']) }}>
4 | {{ $value ?? $slot }}
5 |
6 |
--------------------------------------------------------------------------------
/public/images/Sharktopus.vs.Whalewolf.2015.1080p.BluRay.x265-RARBG-[rarbg.to].torrent:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iceburgcrm/iceburgsaas/HEAD/public/images/Sharktopus.vs.Whalewolf.2015.1080p.BluRay.x265-RARBG-[rarbg.to].torrent
--------------------------------------------------------------------------------
/resources/js/Components/SectionBorder.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | merge(['class' => 'block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition']) }}>{{ $slot }}
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
3 | *.blade.php diff=html
4 | *.css diff=css
5 | *.html diff=html
6 | *.md diff=markdown
7 | *.php diff=php
8 |
9 | /.github export-ignore
10 | CHANGELOG.md export-ignore
11 | .styleci.yml export-ignore
12 |
--------------------------------------------------------------------------------
/app/Models/saas.php:
--------------------------------------------------------------------------------
1 | merge(['class' => 'rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50']) !!}>
2 |
--------------------------------------------------------------------------------
/resources/views/emails/support.blade.php:
--------------------------------------------------------------------------------
1 |
2 | # Support Message
3 | {{ auth()->user()->name }}
4 |
5 | {{ $subject }}
6 |
7 | {{ $message }}
8 |
9 |
10 | Thanks,
11 | {{ config('app.name') }}
12 |
13 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/input.blade.php:
--------------------------------------------------------------------------------
1 | @props(['disabled' => false])
2 |
3 | merge(['class' => 'border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm']) !!}>
4 |
--------------------------------------------------------------------------------
/app/Models/CrmStatus.php:
--------------------------------------------------------------------------------
1 |
2 | defineProps({
3 | message: String,
4 | });
5 |
6 |
7 |
8 |
9 |
10 | {{ message }}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/Models/Iceburg/FieldType.php:
--------------------------------------------------------------------------------
1 |
2 | defineProps({
3 | value: String,
4 | });
5 |
6 |
7 |
8 |
9 | {{ value }}
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/public/dist/assets/logos/Cross.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.{yml,yaml}]
15 | indent_size = 2
16 |
17 | [docker-compose.yml]
18 | indent_size = 4
19 |
--------------------------------------------------------------------------------
/app/Models/Membership.php:
--------------------------------------------------------------------------------
1 | assertTrue(true);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/Http/Controllers/StripeController.php:
--------------------------------------------------------------------------------
1 | user()->redirectToBillingPortal(
12 | route('dashboard')
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/authentication-card.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $logo }}
4 |
5 |
6 |
7 | {{ $slot }}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/Http/Middleware/EncryptCookies.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | protected $except = [
15 | //
16 | ];
17 | }
18 |
--------------------------------------------------------------------------------
/app/Actions/Jetstream/DeleteTeam.php:
--------------------------------------------------------------------------------
1 | purge();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/resources/js/Components/AuthenticationCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/button.blade.php:
--------------------------------------------------------------------------------
1 | merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring focus:ring-gray-300 disabled:opacity-25 transition']) }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/app/Http/Middleware/VerifyCsrfToken.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | protected $except = [
15 | 'stripe/*',
16 | ];
17 | }
18 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | merge(['type' => 'button', 'class' => 'inline-flex items-center justify-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 focus:outline-none focus:border-red-700 focus:ring focus:ring-red-200 active:bg-red-600 disabled:opacity-25 transition']) }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/section-title.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $title }}
4 |
5 |
6 | {{ $description }}
7 |
8 |
9 |
10 |
11 | {{ $aside ?? '' }}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/Actions/Fortify/PasswordValidationRules.php:
--------------------------------------------------------------------------------
1 | hasMany(Module::class, 'id', 'module_id');
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/Models/Iceburg/SubpanelField.php:
--------------------------------------------------------------------------------
1 | hasOne(Field::class, 'id', 'field_id')->with('module');
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/resources/js/Components/ActionMessage.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/validation-errors.blade.php:
--------------------------------------------------------------------------------
1 | @if ($errors->any())
2 |
3 |
{{ __('Whoops! Something went wrong.') }}
4 |
5 |
6 | @foreach ($errors->all() as $error)
7 | {{ $error }}
8 | @endforeach
9 |
10 |
11 | @endif
12 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/secondary-button.blade.php:
--------------------------------------------------------------------------------
1 | merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:ring focus:ring-blue-200 active:text-gray-800 active:bg-gray-50 disabled:opacity-25 transition']) }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/app/Http/Middleware/PreventRequestsDuringMaintenance.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | protected $except = [
15 | //
16 | ];
17 | }
18 |
--------------------------------------------------------------------------------
/app/Http/Middleware/TrustHosts.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | public function hosts()
15 | {
16 | return [
17 | $this->allSubdomainsOfApplicationUrl(),
18 | ];
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/Http/Middleware/TrimStrings.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | protected $except = [
15 | 'current_password',
16 | 'password',
17 | 'password_confirmation',
18 | ];
19 | }
20 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/application-mark.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/tests/Feature/ExampleTest.php:
--------------------------------------------------------------------------------
1 | get('/');
18 |
19 | $response->assertStatus(200);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/resources/js/Components/ApplicationMark.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CreatesApplication.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class)->bootstrap();
19 |
20 | return $app;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/Providers/BroadcastServiceProvider.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/action-section.blade.php:
--------------------------------------------------------------------------------
1 | merge(['class' => 'md:grid md:grid-cols-3 md:gap-6']) }}>
2 |
3 | {{ $title }}
4 | {{ $description }}
5 |
6 |
7 |
8 |
9 | {{ $content }}
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/authentication-card-logo.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/dialog-modal.blade.php:
--------------------------------------------------------------------------------
1 | @props(['id' => null, 'maxWidth' => null])
2 |
3 |
4 |
5 |
6 | {{ $title }}
7 |
8 |
9 |
10 | {{ $content }}
11 |
12 |
13 |
14 |
15 | {{ $footer }}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/database/factories/Iceburg/FieldFactory.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class FieldFactory extends Factory
11 | {
12 | /**
13 | * Define the model's default state.
14 | *
15 | * @return array
16 | */
17 | public function definition()
18 | {
19 |
20 | return [
21 |
22 | ];
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/action-message.blade.php:
--------------------------------------------------------------------------------
1 | @props(['on'])
2 |
3 | merge(['class' => 'text-sm text-gray-600']) }}>
9 | {{ $slot->isEmpty() ? 'Saved.' : $slot }}
10 |
11 |
--------------------------------------------------------------------------------
/resources/js/Components/SectionTitle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import laravel from 'laravel-vite-plugin';
3 | import vue from '@vitejs/plugin-vue';
4 |
5 | export default defineConfig({
6 | plugins: [
7 | laravel({
8 | input: 'resources/js/app.js',
9 | refresh: true,
10 | }),
11 | vue({
12 | template: {
13 | transformAssetUrls: {
14 | base: null,
15 | includeAbsolute: false,
16 | },
17 | },
18 | }),
19 | ],
20 | });
21 |
--------------------------------------------------------------------------------
/app/Listeners/CreateNewCRM.php:
--------------------------------------------------------------------------------
1 | create($event->data);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/database/factories/Iceburg/FieldTypeFactory.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class FieldTypeFactory extends Factory
11 | {
12 | /**
13 | * Define the model's default state.
14 | *
15 | * @return array
16 | */
17 | public function definition()
18 | {
19 | return [
20 | //
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/resources/js/Components/PrimaryButton.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/database/factories/Iceburg/BlankDataRecordFactory.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class BlankDataRecordFactory extends Factory
11 | {
12 | /**
13 | * Define the model's default state.
14 | *
15 | * @return array
16 | */
17 | public function definition()
18 | {
19 | return [
20 | //
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/resources/js/Components/DangerButton.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/Http/Middleware/Authenticate.php:
--------------------------------------------------------------------------------
1 | expectsJson()) {
18 | return route('login');
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Http/Middleware/ValidateSignature.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | protected $except = [
15 | // 'fbclid',
16 | // 'utm_campaign',
17 | // 'utm_content',
18 | // 'utm_medium',
19 | // 'utm_source',
20 | // 'utm_term',
21 | ];
22 | }
23 |
--------------------------------------------------------------------------------
/resources/js/Components/SecondaryButton.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/resources/js/Pages/Billing.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Hold up! You need an active subscription first.
7 |
8 |
9 | Head to the checkout page
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/public/dist/assets/Underline1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lang/en/pagination.php:
--------------------------------------------------------------------------------
1 | '« Previous',
17 | 'next' => 'Next »',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/app/Models/Iceburg/Role.php:
--------------------------------------------------------------------------------
1 | belongsTo(Module::class, 'id', 'module_id');
18 | }
19 |
20 | public function permissions(): Collection
21 | {
22 | return $this->hasMany(Permission::class, 'role_id', 'id');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/Feature/BrowserSessionsTest.php:
--------------------------------------------------------------------------------
1 | actingAs($user = User::factory()->create());
16 |
17 | $response = $this->delete('/user/other-browser-sessions', [
18 | 'password' => 'password',
19 | ]);
20 |
21 | $response->assertSessionHasNoErrors();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/routes/channels.php:
--------------------------------------------------------------------------------
1 | id === (int) $id;
18 | });
19 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/nav-link.blade.php:
--------------------------------------------------------------------------------
1 | @props(['active'])
2 |
3 | @php
4 | $classes = ($active ?? false)
5 | ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition'
6 | : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition';
7 | @endphp
8 |
9 | merge(['class' => $classes]) }}>
10 | {{ $slot }}
11 |
12 |
--------------------------------------------------------------------------------
/resources/js/Pages/Teams/Create.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 | Create Team
11 |
12 |
13 |
14 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | get('/user', function (Request $request) {
18 | return $request->user();
19 | });
20 |
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | comment(Inspiring::quote());
19 | })->purpose('Display an inspiring quote');
20 |
--------------------------------------------------------------------------------
/app/Listeners/StripeEventListener.php:
--------------------------------------------------------------------------------
1 | payload['type'] === 'invoice.payment_succeeded') {
19 | // Handle the incoming event...
20 | echo 'sdsdsdsdsds';
21 | Log::emergency('test');
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 |
2 |
3 | Options -MultiViews -Indexes
4 |
5 |
6 | RewriteEngine On
7 |
8 | # Handle Authorization Header
9 | RewriteCond %{HTTP:Authorization} .
10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
11 |
12 | # Redirect Trailing Slashes If Not A Folder...
13 | RewriteCond %{REQUEST_FILENAME} !-d
14 | RewriteCond %{REQUEST_URI} (.+)/$
15 | RewriteRule ^ %1 [L,R=301]
16 |
17 | # Send Requests To Front Controller...
18 | RewriteCond %{REQUEST_FILENAME} !-d
19 | RewriteCond %{REQUEST_FILENAME} !-f
20 | RewriteRule ^ index.php [L]
21 |
22 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/responsive-nav-link.blade.php:
--------------------------------------------------------------------------------
1 | @props(['active'])
2 |
3 | @php
4 | $classes = ($active ?? false)
5 | ? 'block pl-3 pr-4 py-2 border-l-4 border-indigo-400 text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition'
6 | : 'block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition';
7 | @endphp
8 |
9 | merge(['class' => $classes]) }}>
10 | {{ $slot }}
11 |
12 |
--------------------------------------------------------------------------------
/database/factories/Iceburg/ModuleFactory.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class ModuleFactory extends Factory
12 | {
13 | /**
14 | * Define the model's default state.
15 | *
16 | * @return array
17 | */
18 | public function definition()
19 | {
20 | return [
21 | 'name' => $this->faker->name(),
22 | 'table_name' => Str::random(10),
23 | 'type' => 'default',
24 | ];
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Feature/CreateTeamTest.php:
--------------------------------------------------------------------------------
1 | actingAs($user = User::factory()->withPersonalTeam()->create());
16 |
17 | $response = $this->post('/teams', [
18 | 'name' => 'Test Team',
19 | ]);
20 |
21 | $this->assertCount(2, $user->fresh()->ownedTeams);
22 | $this->assertEquals('Test Team', $user->fresh()->ownedTeams()->latest('id')->first()->name);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/resources/js/Components/ActionSection.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tests/Feature/UpdateTeamNameTest.php:
--------------------------------------------------------------------------------
1 | actingAs($user = User::factory()->withPersonalTeam()->create());
16 |
17 | $response = $this->put('/teams/'.$user->currentTeam->id, [
18 | 'name' => 'Test Team',
19 | ]);
20 |
21 | $this->assertCount(1, $user->fresh()->ownedTeams);
22 | $this->assertEquals('Test Team', $user->currentTeam->fresh()->name);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/Models/TeamInvitation.php:
--------------------------------------------------------------------------------
1 | belongsTo(Jetstream::teamModel());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/Providers/AuthServiceProvider.php:
--------------------------------------------------------------------------------
1 | TeamPolicy::class,
18 | ];
19 |
20 | /**
21 | * Register any authentication / authorization services.
22 | *
23 | * @return void
24 | */
25 | public function boot()
26 | {
27 | $this->registerPolicies();
28 |
29 | //
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/Models/Iceburg/RelationshipModule.php:
--------------------------------------------------------------------------------
1 | belongsTo(Relationship::class);
19 | }
20 |
21 | public function modulefields()
22 | {
23 | return $this->belongsTo(Module::class)->with('fields');
24 | }
25 |
26 | public function module()
27 | {
28 | return $this->belongsTo(Module::class);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_11_182343_create_saas_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->timestamps();
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function down()
28 | {
29 | Schema::dropIfExists('saas');
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/app/Models/Iceburg/ModuleGroup.php:
--------------------------------------------------------------------------------
1 | hasMany(Module::class);
17 | }
18 |
19 | public function getReviewAvgAttribute()
20 | {
21 |
22 | $reviews = $this->modules()->where('status', 1)->get();
23 | $total = 0;
24 | foreach ($reviews as $review) {
25 | $total += $review->review_avg;
26 | }
27 |
28 | return $total / $reviews->count();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/database/factories/TeamFactory.php:
--------------------------------------------------------------------------------
1 | $this->faker->unique()->company(),
27 | 'user_id' => User::factory(),
28 | 'personal_team' => true,
29 | ];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lang/en/auth.php:
--------------------------------------------------------------------------------
1 | 'These credentials do not match our records.',
17 | 'password' => 'The provided password is incorrect.',
18 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
19 |
20 | ];
21 |
--------------------------------------------------------------------------------
/resources/js/Components/AuthenticationCardLogo.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/resources/js/Components/TextInput.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
28 |
29 |
--------------------------------------------------------------------------------
/tests/Feature/ProfileInformationTest.php:
--------------------------------------------------------------------------------
1 | actingAs($user = User::factory()->create());
16 |
17 | $response = $this->put('/user/profile-information', [
18 | 'name' => 'Test Name',
19 | 'email' => 'test@example.com',
20 | ]);
21 |
22 | $this->assertEquals('Test Name', $user->fresh()->name);
23 | $this->assertEquals('test@example.com', $user->fresh()->email);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/public/dist/assets/logos/Menu.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/resources/js/Components/DaysSince.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Expiring tonight
4 |
{{ daysSince }} day left
5 |
{{ daysSince }} days left
6 |
7 |
8 |
9 |
31 |
--------------------------------------------------------------------------------
/app/Http/Middleware/TrustProxies.php:
--------------------------------------------------------------------------------
1 | |string|null
14 | */
15 | protected $proxies;
16 |
17 | /**
18 | * The headers that should be used to detect proxies.
19 | *
20 | * @var int
21 | */
22 | protected $headers =
23 | Request::HEADER_X_FORWARDED_FOR |
24 | Request::HEADER_X_FORWARDED_HOST |
25 | Request::HEADER_X_FORWARDED_PORT |
26 | Request::HEADER_X_FORWARDED_PROTO |
27 | Request::HEADER_X_FORWARDED_AWS_ELB;
28 | }
29 |
--------------------------------------------------------------------------------
/database/migrations/2023_01_30_050504_create_crm_statuses_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->string('name', 32);
19 | $table->timestamps();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | *
26 | * @return void
27 | */
28 | public function down()
29 | {
30 | Schema::dropIfExists('crm_status');
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_20_224345_create_crm_themes_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->string('name', 200);
19 | $table->timestamps();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | *
26 | * @return void
27 | */
28 | public function down()
29 | {
30 | Schema::dropIfExists('crm_themes');
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/app/Actions/Fortify/ResetUserPassword.php:
--------------------------------------------------------------------------------
1 | $this->passwordRules(),
23 | ])->validate();
24 |
25 | $user->forceFill([
26 | 'password' => Hash::make($input['password']),
27 | ])->save();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/resources/views/app.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ config('app.name', 'Laravel') }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | @routes
16 | @vite(['resources/js/app.js', "resources/js/Pages/{$page['component']}.vue"])
17 | @inertiaHead
18 |
19 |
20 | @inertia
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | command('inspire')->hourly();
18 | $schedule->command('delete:expired_databases')->hourly();
19 | }
20 |
21 | /**
22 | * Register the commands for the application.
23 | *
24 | * @return void
25 | */
26 | protected function commands()
27 | {
28 | $this->load(__DIR__.'/Commands');
29 |
30 | require base_path('routes/console.php');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/Events/NewCrm.php:
--------------------------------------------------------------------------------
1 | data = $data;
22 | }
23 |
24 | /**
25 | * Get the channels the event should broadcast on.
26 | *
27 | * @return \Illuminate\Broadcasting\Channel|array
28 | */
29 | public function broadcastOn()
30 | {
31 | return new PrivateChannel('channel-name');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lang/en/passwords.php:
--------------------------------------------------------------------------------
1 | 'Your password has been reset!',
17 | 'sent' => 'We have emailed your password reset link!',
18 | 'throttled' => 'Please wait before retrying.',
19 | 'token' => 'This password reset token is invalid.',
20 | 'user' => "We can't find a user with that email address.",
21 |
22 | ];
23 |
--------------------------------------------------------------------------------
/resources/js/Components/NavLink.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/Http/Middleware/Cors.php:
--------------------------------------------------------------------------------
1 | string('email')->index();
18 | $table->string('token');
19 | $table->timestamp('created_at')->nullable();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | *
26 | * @return void
27 | */
28 | public function down()
29 | {
30 | Schema::dropIfExists('password_resets');
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/resources/js/Components/Checkbox.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
36 |
37 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const defaultTheme = require('tailwindcss/defaultTheme');
2 |
3 | /** @type {import('tailwindcss').Config} */
4 | module.exports = {
5 | content: [
6 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
7 | './vendor/laravel/jetstream/**/*.blade.php',
8 | './storage/framework/views/*.php',
9 | './resources/views/**/*.blade.php',
10 | './resources/js/**/*.vue',
11 | ],
12 |
13 | theme: {
14 | extend: {
15 | fontFamily: {
16 | sans: ['Nunito', ...defaultTheme.fontFamily.sans],
17 | },
18 | },
19 | },
20 | daisyui: {
21 | themes: [
22 |
23 | 'winter'
24 |
25 |
26 | ]
27 | },
28 |
29 | plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography'),require("daisyui")],
30 | };
31 |
--------------------------------------------------------------------------------
/app/Actions/Jetstream/UpdateTeamName.php:
--------------------------------------------------------------------------------
1 | authorize('update', $team);
21 |
22 | Validator::make($input, [
23 | 'name' => ['required', 'string', 'max:255'],
24 | ])->validateWithBag('updateTeamName');
25 |
26 | $team->forceFill([
27 | 'name' => $input['name'],
28 | ])->save();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/database/migrations/2023_01_04_025540_create_crm_types_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->string('name', 100)->default('');
19 | $table->text('description')->nullable();
20 | $table->timestamps();
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | *
27 | * @return void
28 | */
29 | public function down()
30 | {
31 | Schema::dropIfExists('crm_types');
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/database/migrations/2015_05_21_100000_create_teams_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->foreignId('user_id')->index();
19 | $table->string('name');
20 | $table->boolean('personal_team');
21 | $table->timestamps();
22 | });
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | *
28 | * @return void
29 | */
30 | public function down()
31 | {
32 | Schema::dropIfExists('teams');
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/app/Jobs/GenerateCrm.php:
--------------------------------------------------------------------------------
1 | crm = $crm;
26 | $this->user = $user;
27 | }
28 |
29 | /**
30 | * Execute the job.
31 | *
32 | * @return void
33 | */
34 | public function handle()
35 | {
36 | $this->crm->create();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/resources/js/Components/DropdownLink.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/switchable-team.blade.php:
--------------------------------------------------------------------------------
1 | @props(['team', 'component' => 'jet-dropdown-link'])
2 |
3 |
20 |
--------------------------------------------------------------------------------
/app/Models/BillingPortal.php:
--------------------------------------------------------------------------------
1 | `${title} - ${appName}`,
14 | resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
15 | setup({ el, app, props, plugin }) {
16 | return createApp({ render: () => h(app, props) })
17 | .use(plugin)
18 | .use(ZiggyVue, Ziggy)
19 | .mount(el);
20 | },
21 | });
22 |
23 | InertiaProgress.init({ color: '#4B5563' });
24 |
--------------------------------------------------------------------------------
/public/dist/assets/logos/Lightning.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "vite",
5 | "build": "vite build",
6 | "watch": "vite build --watch"
7 | },
8 | "devDependencies": {
9 | "@inertiajs/inertia": "^0.11.0",
10 | "@inertiajs/inertia-vue3": "^0.6.0",
11 | "@inertiajs/progress": "^0.2.7",
12 | "@tailwindcss/forms": "^0.5.2",
13 | "@tailwindcss/typography": "^0.5.2",
14 | "@vitejs/plugin-vue": "^4.0.0",
15 | "autoprefixer": "^10.4.7",
16 | "axios": "^1.1.2",
17 | "laravel-vite-plugin": "^0.7.2",
18 | "lodash": "^4.17.19",
19 | "postcss": "^8.4.14",
20 | "tailwindcss": "^3.1.0",
21 | "vite": "^4.0.0",
22 | "vue": "^3.2.31",
23 | "vue-stripe-js": "^1.0.1"
24 | },
25 | "dependencies": {
26 | "@vue-stripe/vue-stripe": "^4.5.0",
27 | "daisyui": "^2.47.0",
28 | "vue-router": "^4.1.6"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/database/migrations/2020_05_21_200000_create_team_user_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->foreignId('team_id');
19 | $table->foreignId('user_id');
20 | $table->string('role')->nullable();
21 | $table->timestamps();
22 |
23 | $table->unique(['team_id', 'user_id']);
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('team_user');
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/resources/js/Pages/API/Index.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 | API Tokens
17 |
18 |
19 |
20 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/Http/Middleware/RedirectIfAuthenticated.php:
--------------------------------------------------------------------------------
1 | check()) {
25 | return redirect(RouteServiceProvider::HOME);
26 | }
27 | }
28 |
29 | return $next($request);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/config/cors.php:
--------------------------------------------------------------------------------
1 | ['api/*', 'sanctum/csrf-cookie'],
19 |
20 | 'allowed_methods' => ['*'],
21 |
22 | 'allowed_origins' => ['*'],
23 |
24 | 'allowed_origins_patterns' => [],
25 |
26 | 'allowed_headers' => ['*'],
27 |
28 | 'exposed_headers' => [],
29 |
30 | 'max_age' => 0,
31 |
32 | 'supports_credentials' => false,
33 |
34 | ];
35 |
--------------------------------------------------------------------------------
/tests/Feature/DeleteApiTokenTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('API support is not enabled.');
19 | }
20 |
21 | $this->actingAs($user = User::factory()->withPersonalTeam()->create());
22 |
23 | $token = $user->tokens()->create([
24 | 'name' => 'Test Token',
25 | 'token' => Str::random(40),
26 | 'abilities' => ['create', 'read'],
27 | ]);
28 |
29 | $response = $this->delete('/user/api-tokens/'.$token->id);
30 |
31 | $this->assertCount(0, $user->fresh()->tokens);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/database/migrations/2019_08_19_000000_create_failed_jobs_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->string('uuid')->unique();
19 | $table->text('connection');
20 | $table->text('queue');
21 | $table->longText('payload');
22 | $table->longText('exception');
23 | $table->timestamp('failed_at')->useCurrent();
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('failed_jobs');
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/database/migrations/2020_05_21_300000_create_team_invitations_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->foreignId('team_id')->constrained()->cascadeOnDelete();
19 | $table->string('email');
20 | $table->string('role')->nullable();
21 | $table->timestamps();
22 |
23 | $table->unique(['team_id', 'email']);
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('team_invitations');
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/database/migrations/2023_01_02_050520_create_sessions_table.php:
--------------------------------------------------------------------------------
1 | string('id')->primary();
18 | $table->foreignId('user_id')->nullable()->index();
19 | $table->string('ip_address', 45)->nullable();
20 | $table->text('user_agent')->nullable();
21 | $table->longText('payload');
22 | $table->integer('last_activity')->index();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | *
29 | * @return void
30 | */
31 | public function down()
32 | {
33 | Schema::dropIfExists('sessions');
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/public/dist/assets/Highlight3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/Actions/Fortify/UpdateUserPassword.php:
--------------------------------------------------------------------------------
1 | ['required', 'string', 'current_password:web'],
23 | 'password' => $this->passwordRules(),
24 | ], [
25 | 'current_password.current_password' => __('The provided password does not match your current password.'),
26 | ])->validateWithBag('updatePassword');
27 |
28 | $user->forceFill([
29 | 'password' => Hash::make($input['password']),
30 | ])->save();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_04_052358_create_jobs_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->string('queue')->index();
19 | $table->longText('payload');
20 | $table->unsignedTinyInteger('attempts');
21 | $table->unsignedInteger('reserved_at')->nullable();
22 | $table->unsignedInteger('available_at');
23 | $table->unsignedInteger('created_at');
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('jobs');
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/app/Models/Crm.php:
--------------------------------------------------------------------------------
1 | 'date:Y-m-d h:i A',
15 | 'update_at' => 'date:y-m-d h:i A',
16 | ];
17 |
18 | public function type()
19 | {
20 | return $this->hasOne(CrmType::class, 'id', 'type_id');
21 | }
22 |
23 | public function status()
24 | {
25 | return $this->hasOne(CrmStatus::class, 'id', 'status_id');
26 | }
27 |
28 | public static function deleteCRM($id)
29 | {
30 | $crm = Crm::where('id', $id)->first();
31 | $status = 0;
32 | if (auth()->user()->id == 1 || $crm->user_id == auth()->user()->id) {
33 | DB::statement('DROP DATABASE '.$crm->name);
34 | $status = Crm::where('id', $id)->delete();
35 | }
36 |
37 | return $status;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/form-section.blade.php:
--------------------------------------------------------------------------------
1 | @props(['submit'])
2 |
3 | merge(['class' => 'md:grid md:grid-cols-3 md:gap-6']) }}>
4 |
5 | {{ $title }}
6 | {{ $description }}
7 |
8 |
9 |
24 |
25 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/mail/team-invitation.blade.php:
--------------------------------------------------------------------------------
1 | @component('mail::message')
2 | {{ __('You have been invited to join the :team team!', ['team' => $invitation->team->name]) }}
3 |
4 | @if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::registration()))
5 | {{ __('If you do not have an account, you may create one by clicking the button below. After creating an account, you may click the invitation acceptance button in this email to accept the team invitation:') }}
6 |
7 | @component('mail::button', ['url' => route('register')])
8 | {{ __('Create Account') }}
9 | @endcomponent
10 |
11 | {{ __('If you already have an account, you may accept this invitation by clicking the button below:') }}
12 |
13 | @else
14 | {{ __('You may accept this invitation by clicking the button below:') }}
15 | @endif
16 |
17 |
18 | @component('mail::button', ['url' => $acceptUrl])
19 | {{ __('Accept Invitation') }}
20 | @endcomponent
21 |
22 | {{ __('If you did not expect to receive an invitation to this team, you may discard this email.') }}
23 | @endcomponent
24 |
--------------------------------------------------------------------------------
/database/migrations/2019_05_03_000003_create_subscription_items_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->foreignId('subscription_id');
17 | $table->string('stripe_id')->unique();
18 | $table->string('stripe_product');
19 | $table->string('stripe_price');
20 | $table->integer('quantity')->nullable();
21 | $table->timestamps();
22 |
23 | $table->unique(['subscription_id', 'stripe_price']);
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | */
30 | public function down(): void
31 | {
32 | Schema::dropIfExists('subscription_items');
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/app/Actions/Jetstream/CreateTeam.php:
--------------------------------------------------------------------------------
1 | authorize('create', Jetstream::newTeamModel());
22 |
23 | Validator::make($input, [
24 | 'name' => ['required', 'string', 'max:255'],
25 | ])->validateWithBag('createTeam');
26 |
27 | AddingTeam::dispatch($user);
28 |
29 | $user->switchTeam($team = $user->ownedTeams()->create([
30 | 'name' => $input['name'],
31 | 'personal_team' => false,
32 | ]));
33 |
34 | return $team;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Models/Team.php:
--------------------------------------------------------------------------------
1 | 'boolean',
22 | ];
23 |
24 | /**
25 | * The attributes that are mass assignable.
26 | *
27 | * @var string[]
28 | */
29 | protected $fillable = [
30 | 'name',
31 | 'personal_team',
32 | ];
33 |
34 | /**
35 | * The event map for the model.
36 | *
37 | * @var array
38 | */
39 | protected $dispatchesEvents = [
40 | 'created' => TeamCreated::class,
41 | 'updated' => TeamUpdated::class,
42 | 'deleted' => TeamDeleted::class,
43 | ];
44 | }
45 |
--------------------------------------------------------------------------------
/app/Http/Controllers/InvoiceController.php:
--------------------------------------------------------------------------------
1 | invoicesIncludingPending()->map(function ($invoice) {
20 | return [
21 | 'description' => $invoice->lines->data[0]->description,
22 | 'created' => $invoice->created,
23 | 'paid' => $invoice->paid,
24 | 'status' => $invoice->status,
25 | 'url' => $invoice->hosted_invoice_url ?: null,
26 | ];
27 | });
28 |
29 | return Inertia::render('BillingPortal/Invoice/Index', [
30 | 'invoices' => $invoices->toArray(),
31 | ]);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/database/migrations/2019_05_03_000001_create_customer_columns.php:
--------------------------------------------------------------------------------
1 | string('stripe_id')->nullable()->index();
16 | $table->string('pm_type')->nullable();
17 | $table->string('pm_last_four', 4)->nullable();
18 | $table->timestamp('trial_ends_at')->nullable();
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | */
25 | public function down(): void
26 | {
27 | Schema::table('users', function (Blueprint $table) {
28 | $table->dropColumn([
29 | 'stripe_id',
30 | 'pm_type',
31 | 'pm_last_four',
32 | 'trial_ends_at',
33 | ]);
34 | });
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/database/migrations/2023_01_25_160524_create_crm_plans_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->string('name', 50)->default('');
19 | $table->string('description', 200)->default('');
20 | $table->integer('price')->default(0);
21 | $table->string('stripe_id', 64)->default('');
22 | $table->string('logo', 64)->default('');
23 | $table->integer('status')->default(1);
24 | $table->timestamps();
25 | });
26 | }
27 |
28 | /**
29 | * Reverse the migrations.
30 | *
31 | * @return void
32 | */
33 | public function down()
34 | {
35 | Schema::dropIfExists('crm_plans');
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/resources/js/Components/ResponsiveNavLink.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->morphs('tokenable');
19 | $table->string('name');
20 | $table->string('token', 64)->unique();
21 | $table->text('abilities')->nullable();
22 | $table->timestamp('last_used_at')->nullable();
23 | $table->timestamp('expires_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::dropIfExists('personal_access_tokens');
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/resources/js/Components/DialogModal.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/Http/Middleware/HandleInertiaRequests.php:
--------------------------------------------------------------------------------
1 | $request->user() ? $request->user()->subscribed('default') : false,
38 | ]);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Feature/CreateApiTokenTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('API support is not enabled.');
18 | }
19 |
20 | $this->actingAs($user = User::factory()->withPersonalTeam()->create());
21 |
22 | $response = $this->post('/user/api-tokens', [
23 | 'name' => 'Test Token',
24 | 'permissions' => [
25 | 'read',
26 | 'update',
27 | ],
28 | ]);
29 |
30 | $this->assertCount(1, $user->fresh()->tokens);
31 | $this->assertEquals('Test Token', $user->fresh()->tokens->first()->name);
32 | $this->assertTrue($user->fresh()->tokens->first()->can('read'));
33 | $this->assertFalse($user->fresh()->tokens->first()->can('delete'));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/public/dist/script.js:
--------------------------------------------------------------------------------
1 | window.addEventListener("load", function () {
2 | console.log(document.querySelector("#showMenu"));
3 | document
4 | .querySelector("#showMenu")
5 | .addEventListener("click", function (event) {
6 | document.querySelector("#mobileNav").classList.remove("hidden");
7 | });
8 |
9 | document
10 | .querySelector("#hideMenu")
11 | .addEventListener("click", function (event) {
12 | document.querySelector("#mobileNav").classList.add("hidden");
13 | });
14 |
15 | document.querySelectorAll("[toggleElement]").forEach((toggle) => {
16 | toggle.addEventListener("click", function (event) {
17 | console.log(toggle);
18 | const answerElement = toggle.querySelector("[answer]");
19 | const caretElement = toggle.querySelector("img");
20 | console.log(answerElement);
21 | if (answerElement.classList.contains("hidden")) {
22 | answerElement.classList.remove("hidden");
23 | caretElement.classList.add("rotate-90");
24 | } else {
25 | answerElement.classList.add("hidden");
26 | caretElement.classList.remove("rotate-90");
27 | }
28 | });
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/public/dist/assets/logos/Facebook.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/config/view.php:
--------------------------------------------------------------------------------
1 | [
17 | resource_path('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' => env(
32 | 'VIEW_COMPILED_PATH',
33 | realpath(storage_path('framework/views'))
34 | ),
35 |
36 | ];
37 |
--------------------------------------------------------------------------------
/database/migrations/2019_05_03_000002_create_subscriptions_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->foreignId('user_id');
17 | $table->string('name');
18 | $table->string('stripe_id')->unique();
19 | $table->string('stripe_status');
20 | $table->string('stripe_price')->nullable();
21 | $table->integer('quantity')->nullable();
22 | $table->timestamp('trial_ends_at')->nullable();
23 | $table->timestamp('ends_at')->nullable();
24 | $table->timestamps();
25 |
26 | $table->index(['user_id', 'stripe_status']);
27 | });
28 | }
29 |
30 | /**
31 | * Reverse the migrations.
32 | */
33 | public function down(): void
34 | {
35 | Schema::dropIfExists('subscriptions');
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/public/dist/assets/Highlight2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/tests/Feature/LeaveTeamTest.php:
--------------------------------------------------------------------------------
1 | withPersonalTeam()->create();
16 |
17 | $user->currentTeam->users()->attach(
18 | $otherUser = User::factory()->create(), ['role' => 'admin']
19 | );
20 |
21 | $this->actingAs($otherUser);
22 |
23 | $response = $this->delete('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->id);
24 |
25 | $this->assertCount(0, $user->currentTeam->fresh()->users);
26 | }
27 |
28 | public function test_team_owners_cant_leave_their_own_team()
29 | {
30 | $this->actingAs($user = User::factory()->withPersonalTeam()->create());
31 |
32 | $response = $this->delete('/teams/'.$user->currentTeam->id.'/members/'.$user->id);
33 |
34 | $response->assertSessionHasErrorsIn('removeTeamMember', ['team']);
35 |
36 | $this->assertNotNull($user->currentTeam->fresh());
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/database/migrations/2023_01_04_025708_create_crms_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->integer('user_id')->default(0);
19 | $table->string('name', 50)->default('');
20 | $table->string('label', 50)->default('');
21 | $table->string('user_crm_name', 50)->default('');
22 | $table->integer('type_id')->default(1);
23 | $table->string('server', 32)->default('oracle4');
24 | $table->string('plan', 32)->default(1);
25 | $table->integer('status_id')->default(1);
26 | $table->timestamps();
27 | });
28 | }
29 |
30 | /**
31 | * Reverse the migrations.
32 | *
33 | * @return void
34 | */
35 | public function down()
36 | {
37 | Schema::dropIfExists('crms');
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/public/dist/assets/Highlight1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/tests/Feature/DeleteTeamTest.php:
--------------------------------------------------------------------------------
1 | actingAs($user = User::factory()->withPersonalTeam()->create());
17 |
18 | $user->ownedTeams()->save($team = Team::factory()->make([
19 | 'personal_team' => false,
20 | ]));
21 |
22 | $team->users()->attach(
23 | $otherUser = User::factory()->create(), ['role' => 'test-role']
24 | );
25 |
26 | $response = $this->delete('/teams/'.$team->id);
27 |
28 | $this->assertNull($team->fresh());
29 | $this->assertCount(0, $otherUser->fresh()->teams);
30 | }
31 |
32 | public function test_personal_teams_cant_be_deleted()
33 | {
34 | $this->actingAs($user = User::factory()->withPersonalTeam()->create());
35 |
36 | $response = $this->delete('/teams/'.$user->currentTeam->id);
37 |
38 | $this->assertNotNull($user->currentTeam->fresh());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Models/Iceburg/User.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | protected $fillable = [
22 | 'name',
23 | 'email',
24 | 'password',
25 | ];
26 |
27 | /**
28 | * The attributes that should be hidden for serialization.
29 | *
30 | * @var array
31 | */
32 | protected $hidden = [
33 | 'password',
34 | 'remember_token',
35 | ];
36 |
37 | /**
38 | * The attributes that should be cast.
39 | *
40 | * @var array
41 | */
42 | protected $casts = [
43 | 'email_verified_at' => 'datetime',
44 | ];
45 |
46 | public function role()
47 | {
48 | return $this->hasOne(Role::class, 'id', 'role_id');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/Feature/AuthenticationTest.php:
--------------------------------------------------------------------------------
1 | get('/login');
17 |
18 | $response->assertStatus(200);
19 | }
20 |
21 | public function test_users_can_authenticate_using_the_login_screen()
22 | {
23 | $user = User::factory()->create();
24 |
25 | $response = $this->post('/login', [
26 | 'email' => $user->email,
27 | 'password' => 'password',
28 | ]);
29 |
30 | $this->assertAuthenticated();
31 | $response->assertRedirect(RouteServiceProvider::HOME);
32 | }
33 |
34 | public function test_users_can_not_authenticate_with_invalid_password()
35 | {
36 | $user = User::factory()->create();
37 |
38 | $this->post('/login', [
39 | 'email' => $user->email,
40 | 'password' => 'wrong-password',
41 | ]);
42 |
43 | $this->assertGuest();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/database/migrations/2023_01_31_212727_create_connections_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->integer('user_id');
19 | $table->string('host', 32);
20 | $table->string('database', 32);
21 | $table->string('collation', 32)->default('utf8mb4');
22 | $table->string('charset', 32)->default('utf8mb4_unicode_ci');
23 | $table->string('username', 32);
24 | $table->string('password', 32);
25 | $table->string('port', 8)->default('3306');
26 | $table->integer('status')->default(1);
27 | $table->timestamps();
28 | });
29 | }
30 |
31 | /**
32 | * Reverse the migrations.
33 | *
34 | * @return void
35 | */
36 | public function down()
37 | {
38 | Schema::dropIfExists('connections');
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/app/Exceptions/Handler.php:
--------------------------------------------------------------------------------
1 | , \Psr\Log\LogLevel::*>
14 | */
15 | protected $levels = [
16 | //
17 | ];
18 |
19 | /**
20 | * A list of the exception types that are not reported.
21 | *
22 | * @var array>
23 | */
24 | protected $dontReport = [
25 | //
26 | ];
27 |
28 | /**
29 | * A list of the inputs that are never flashed to the session on validation exceptions.
30 | *
31 | * @var array
32 | */
33 | protected $dontFlash = [
34 | 'current_password',
35 | 'password',
36 | 'password_confirmation',
37 | ];
38 |
39 | /**
40 | * Register the exception handling callbacks for the application.
41 | *
42 | * @return void
43 | */
44 | public function register()
45 | {
46 | $this->reportable(function (Throwable $e) {
47 | //
48 | });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/Feature/RemoveTeamMemberTest.php:
--------------------------------------------------------------------------------
1 | actingAs($user = User::factory()->withPersonalTeam()->create());
16 |
17 | $user->currentTeam->users()->attach(
18 | $otherUser = User::factory()->create(), ['role' => 'admin']
19 | );
20 |
21 | $response = $this->delete('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->id);
22 |
23 | $this->assertCount(0, $user->currentTeam->fresh()->users);
24 | }
25 |
26 | public function test_only_team_owner_can_remove_team_members()
27 | {
28 | $user = User::factory()->withPersonalTeam()->create();
29 |
30 | $user->currentTeam->users()->attach(
31 | $otherUser = User::factory()->create(), ['role' => 'admin']
32 | );
33 |
34 | $this->actingAs($otherUser);
35 |
36 | $response = $this->delete('/teams/'.$user->currentTeam->id.'/members/'.$user->id);
37 |
38 | $response->assertStatus(403);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | ./tests/Unit
10 |
11 |
12 | ./tests/Feature
13 |
14 |
15 |
16 |
17 | ./app
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/tests/Feature/DeleteAccountTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Account deletion is not enabled.');
18 | }
19 |
20 | $this->actingAs($user = User::factory()->create());
21 |
22 | $response = $this->delete('/user', [
23 | 'password' => 'password',
24 | ]);
25 |
26 | $this->assertNull($user->fresh());
27 | }
28 |
29 | public function test_correct_password_must_be_provided_before_account_can_be_deleted()
30 | {
31 | if (! Features::hasAccountDeletionFeatures()) {
32 | return $this->markTestSkipped('Account deletion is not enabled.');
33 | }
34 |
35 | $this->actingAs($user = User::factory()->create());
36 |
37 | $response = $this->delete('/user', [
38 | 'password' => 'wrong-password',
39 | ]);
40 |
41 | $this->assertNotNull($user->fresh());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/confirmation-modal.blade.php:
--------------------------------------------------------------------------------
1 | @props(['id' => null, 'maxWidth' => null])
2 |
3 |
4 |
5 |
6 |
11 |
12 |
13 |
14 | {{ $title }}
15 |
16 |
17 |
18 | {{ $content }}
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{ $footer }}
26 |
27 |
28 |
--------------------------------------------------------------------------------
/tests/Feature/PasswordConfirmationTest.php:
--------------------------------------------------------------------------------
1 | withPersonalTeam()->create();
16 |
17 | $response = $this->actingAs($user)->get('/user/confirm-password');
18 |
19 | $response->assertStatus(200);
20 | }
21 |
22 | public function test_password_can_be_confirmed()
23 | {
24 | $user = User::factory()->create();
25 |
26 | $response = $this->actingAs($user)->post('/user/confirm-password', [
27 | 'password' => 'password',
28 | ]);
29 |
30 | $response->assertRedirect();
31 | $response->assertSessionHasNoErrors();
32 | }
33 |
34 | public function test_password_is_not_confirmed_with_invalid_password()
35 | {
36 | $user = User::factory()->create();
37 |
38 | $response = $this->actingAs($user)->post('/user/confirm-password', [
39 | 'password' => 'wrong-password',
40 | ]);
41 |
42 | $response->assertSessionHasErrors();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/public/dist/assets/logos/Heart.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/Providers/EventServiceProvider.php:
--------------------------------------------------------------------------------
1 | >
16 | */
17 | protected $listen = [
18 | Registered::class => [
19 | SendEmailVerificationNotification::class,
20 | ],
21 | UserCreated::class => [
22 | SendRegisterationEmail::class,
23 | ],
24 | WebhookReceived::class => [
25 | StripeEventListener::class,
26 | ],
27 | ];
28 |
29 | /**
30 | * Register any events for your application.
31 | *
32 | * @return void
33 | */
34 | public function boot()
35 | {
36 | //
37 | }
38 |
39 | /**
40 | * Determine if events and listeners should be automatically discovered.
41 | *
42 | * @return bool
43 | */
44 | public function shouldDiscoverEvents()
45 | {
46 | return false;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/resources/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | window._ = _;
3 |
4 | /**
5 | * We'll load the axios HTTP library which allows us to easily issue requests
6 | * to our Laravel back-end. This library automatically handles sending the
7 | * CSRF token as a header based on the value of the "XSRF" token cookie.
8 | */
9 |
10 | import axios from 'axios';
11 | window.axios = axios;
12 |
13 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
14 |
15 | /**
16 | * Echo exposes an expressive API for subscribing to channels and listening
17 | * for events that are broadcast by Laravel. Echo and event broadcasting
18 | * allows your team to easily build robust real-time web applications.
19 | */
20 |
21 | // import Echo from 'laravel-echo';
22 |
23 | // import Pusher from 'pusher-js';
24 | // window.Pusher = Pusher;
25 |
26 | // window.Echo = new Echo({
27 | // broadcaster: 'pusher',
28 | // key: import.meta.env.VITE_PUSHER_APP_KEY,
29 | // wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
30 | // wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
31 | // wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
32 | // forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
33 | // enabledTransports: ['ws', 'wss'],
34 | // });
35 |
--------------------------------------------------------------------------------
/tests/Feature/ApiTokenPermissionsTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('API support is not enabled.');
19 | }
20 |
21 | $this->actingAs($user = User::factory()->withPersonalTeam()->create());
22 |
23 | $token = $user->tokens()->create([
24 | 'name' => 'Test Token',
25 | 'token' => Str::random(40),
26 | 'abilities' => ['create', 'read'],
27 | ]);
28 |
29 | $response = $this->put('/user/api-tokens/'.$token->id, [
30 | 'name' => $token->name,
31 | 'permissions' => [
32 | 'delete',
33 | 'missing-permission',
34 | ],
35 | ]);
36 |
37 | $this->assertTrue($user->fresh()->tokens->first()->can('delete'));
38 | $this->assertFalse($user->fresh()->tokens->first()->can('read'));
39 | $this->assertFalse($user->fresh()->tokens->first()->can('missing-permission'));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/resources/js/Components/Alert.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ message }}
6 |
7 |
8 |
9 |
10 |
11 |
Error! {{ message }}
12 |
13 |
14 |
15 |
29 |
--------------------------------------------------------------------------------
/resources/js/Components/FormSection.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/public/dist/assets/logos/Star.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/dist/assets/logos/CheckedBox.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->string('name');
19 | $table->string('email')->unique();
20 | $table->timestamp('email_verified_at')->nullable();
21 | $table->string('password');
22 | $table->integer('plan_id')->default(1);
23 | $table->string('signup_plan')->default('free');
24 | $table->integer('first_login')->default(1);
25 | $table->integer('google_id')->default(0);
26 | $table->integer('github_id')->default(0);
27 | $table->integer('twitter_id')->default(0);
28 | $table->rememberToken();
29 | $table->foreignId('current_team_id')->nullable();
30 | $table->string('profile_photo_path', 2048)->nullable();
31 | $table->timestamps();
32 | });
33 | }
34 |
35 | /**
36 | * Reverse the migrations.
37 | *
38 | * @return void
39 | */
40 | public function down()
41 | {
42 | Schema::dropIfExists('users');
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/public/dist/assets/logos/Youtube.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/Actions/Jetstream/DeleteUser.php:
--------------------------------------------------------------------------------
1 | deletesTeams = $deletesTeams;
26 | }
27 |
28 | /**
29 | * Delete the given user.
30 | *
31 | * @param mixed $user
32 | * @return void
33 | */
34 | public function delete($user)
35 | {
36 | DB::transaction(function () use ($user) {
37 | $this->deleteTeams($user);
38 | $user->deleteProfilePhoto();
39 | $user->tokens->each->delete();
40 | $user->delete();
41 | });
42 | }
43 |
44 | /**
45 | * Delete the teams and team associations attached to the user.
46 | *
47 | * @param mixed $user
48 | * @return void
49 | */
50 | protected function deleteTeams($user)
51 | {
52 | $user->teams()->detach();
53 |
54 | $user->ownedTeams->each(function ($team) {
55 | $this->deletesTeams->delete($team);
56 | });
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/resources/js/Pages/Teams/Show.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 | Team Settings
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/tests/Feature/UpdateTeamMemberRoleTest.php:
--------------------------------------------------------------------------------
1 | actingAs($user = User::factory()->withPersonalTeam()->create());
16 |
17 | $user->currentTeam->users()->attach(
18 | $otherUser = User::factory()->create(), ['role' => 'admin']
19 | );
20 |
21 | $response = $this->put('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->id, [
22 | 'role' => 'editor',
23 | ]);
24 |
25 | $this->assertTrue($otherUser->fresh()->hasTeamRole(
26 | $user->currentTeam->fresh(), 'editor'
27 | ));
28 | }
29 |
30 | public function test_only_team_owner_can_update_team_member_roles()
31 | {
32 | $user = User::factory()->withPersonalTeam()->create();
33 |
34 | $user->currentTeam->users()->attach(
35 | $otherUser = User::factory()->create(), ['role' => 'admin']
36 | );
37 |
38 | $this->actingAs($otherUser);
39 |
40 | $response = $this->put('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->id, [
41 | 'role' => 'editor',
42 | ]);
43 |
44 | $this->assertTrue($otherUser->fresh()->hasTeamRole(
45 | $user->currentTeam->fresh(), 'admin'
46 | ));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/public/dist/assets/Underline2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | configureRateLimiting();
30 |
31 | $this->routes(function () {
32 | Route::middleware('api')
33 | ->prefix('api')
34 | ->group(base_path('routes/api.php'));
35 |
36 | Route::middleware('web')
37 | ->group(base_path('routes/web.php'));
38 | });
39 | }
40 |
41 | /**
42 | * Configure the rate limiters for the application.
43 | *
44 | * @return void
45 | */
46 | protected function configureRateLimiting()
47 | {
48 | RateLimiter::for('api', function (Request $request) {
49 | return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
50 | });
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/Console/Commands/DeleteExpiredFreeDatabases.php:
--------------------------------------------------------------------------------
1 | subDays(0);
35 | $date = date('Y-m-d H:i:s', strtotime('7 Day'));
36 | $users = User::all();
37 | foreach ($users as $user) {
38 | if (! $user->is_subscribed()) {
39 |
40 | Crm::where('user_id', $user->id)
41 | ->where('created_at', '<=', $date)->each(function ($db) {
42 | DB::statement('DROP DATABASE '.$db->name);
43 | });
44 | Crm::where('user_id', $user->id)
45 | ->where('created_at', '<=', $date)
46 | ->delete();
47 | }
48 | }
49 |
50 | return Command::SUCCESS;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/Providers/FortifyServiceProvider.php:
--------------------------------------------------------------------------------
1 | email;
41 |
42 | return Limit::perMinute(5)->by($email.$request->ip());
43 | });
44 |
45 | RateLimiter::for('two-factor', function (Request $request) {
46 | return Limit::perMinute(5)->by($request->session()->get('login.id'));
47 | });
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php:
--------------------------------------------------------------------------------
1 | text('two_factor_secret')
19 | ->after('password')
20 | ->nullable();
21 |
22 | $table->text('two_factor_recovery_codes')
23 | ->after('two_factor_secret')
24 | ->nullable();
25 |
26 | if (Fortify::confirmsTwoFactorAuthentication()) {
27 | $table->timestamp('two_factor_confirmed_at')
28 | ->after('two_factor_recovery_codes')
29 | ->nullable();
30 | }
31 | });
32 | }
33 |
34 | /**
35 | * Reverse the migrations.
36 | *
37 | * @return void
38 | */
39 | public function down()
40 | {
41 | Schema::table('users', function (Blueprint $table) {
42 | $table->dropColumn(array_merge([
43 | 'two_factor_secret',
44 | 'two_factor_recovery_codes',
45 | ], Fortify::confirmsTwoFactorAuthentication() ? [
46 | 'two_factor_confirmed_at',
47 | ] : []));
48 | });
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/public/dist/assets/logos/Instagram.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/config/services.php:
--------------------------------------------------------------------------------
1 | [
18 | 'domain' => env('MAILGUN_DOMAIN'),
19 | 'secret' => env('MAILGUN_SECRET'),
20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
21 | 'scheme' => 'https',
22 | ],
23 |
24 | 'postmark' => [
25 | 'token' => env('POSTMARK_TOKEN'),
26 | ],
27 |
28 | 'ses' => [
29 | 'key' => env('AWS_ACCESS_KEY_ID'),
30 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
31 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
32 | ],
33 | 'twitter' => [
34 | 'client_id' => env('TWITTER_CLIENT_ID'),
35 | 'client_secret' => env('TWITTER_CLIENT_SECRET'),
36 | 'redirect' => '/auth/twitter/callback',
37 | ],
38 | 'twitter' => [
39 | 'client_id' => env('GOOGLE_CLIENT_ID'),
40 | 'client_secret' => env('GOOGLE_CLIENT_SECRET'),
41 | 'redirect' => '/auth/google/callback',
42 | ],
43 | 'github' => [
44 | 'client_id' => env('GITHUB_CLIENT_ID'),
45 | 'client_secret' => env('GITHUB_CLIENT_SECRET'),
46 | 'redirect' => '/auth/github/callback',
47 | ],
48 |
49 | ];
50 |
--------------------------------------------------------------------------------
/app/Mail/SupportMail.php:
--------------------------------------------------------------------------------
1 | subject2 = $subject;
28 | $this->message = $message;
29 | }
30 |
31 | /**
32 | * Get the message envelope.
33 | *
34 | * @return \Illuminate\Mail\Mailables\Envelope
35 | */
36 | public function envelope()
37 | {
38 | return new Envelope(
39 | subject: 'Support Mail',
40 | from: new Address('support@iceburgcrm.com', 'Support')
41 | );
42 | }
43 |
44 | /**
45 | * Get the message content definition.
46 | *
47 | * @return \Illuminate\Mail\Mailables\Content
48 | */
49 | public function content()
50 | {
51 | return new Content(
52 | markdown: 'emails.support',
53 | with: [
54 | 'subject' => $this->subject2,
55 | 'message' => $this->message,
56 | ]
57 | );
58 | }
59 |
60 | /**
61 | * Get the attachments for the message.
62 | *
63 | * @return array
64 | */
65 | public function attachments()
66 | {
67 | return [];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/dropdown.blade.php:
--------------------------------------------------------------------------------
1 | @props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white', 'dropdownClasses' => ''])
2 |
3 | @php
4 | switch ($align) {
5 | case 'left':
6 | $alignmentClasses = 'origin-top-left left-0';
7 | break;
8 | case 'top':
9 | $alignmentClasses = 'origin-top';
10 | break;
11 | case 'none':
12 | case 'false':
13 | $alignmentClasses = '';
14 | break;
15 | case 'right':
16 | default:
17 | $alignmentClasses = 'origin-top-right right-0';
18 | break;
19 | }
20 |
21 | switch ($width) {
22 | case '48':
23 | $width = 'w-48';
24 | break;
25 | }
26 | @endphp
27 |
28 |
29 |
30 | {{ $trigger }}
31 |
32 |
33 |
43 |
44 | {{ $content }}
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/Models/Iceburg/Setting.php:
--------------------------------------------------------------------------------
1 | pluck('value')->first();
18 | if (! $setting && $key == 'theme') {
19 | return 'light';
20 | }
21 |
22 | return $setting;
23 | }
24 |
25 | public static function getThemes()
26 | {
27 | return DB::table('themes')->get();
28 |
29 | }
30 |
31 | public static function getSettings()
32 | {
33 | $data = [];
34 | Setting::all()->each(function ($item) use (&$data) {
35 | return $data[$item->name] = $item->value;
36 | });
37 |
38 | return $data;
39 | }
40 |
41 | public static function getBreadCrumbs($level1 = null, $level2 = null, $level3 = null)
42 | {
43 | $data[] = ['name' => 'Home', 'url' => '/dashboard', 'svg' => 'home'];
44 | if ($level1 !== null) {
45 | $data[] = $level1;
46 | }
47 | if ($level2 !== null) {
48 | $data[] = $level2;
49 | }
50 | if ($level3 !== null) {
51 | $data[] = $level3;
52 | }
53 |
54 | return $data;
55 | }
56 |
57 | public static function saveSettings($setting)
58 | {
59 | foreach ($setting as $key => $value) {
60 | Setting::where('name', $key)->update(['value' => $value]);
61 | }
62 |
63 | return 1;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/Feature/RegistrationTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Registration support is not enabled.');
19 | }
20 |
21 | $response = $this->get('/register');
22 |
23 | $response->assertStatus(200);
24 | }
25 |
26 | public function test_registration_screen_cannot_be_rendered_if_support_is_disabled()
27 | {
28 | if (Features::enabled(Features::registration())) {
29 | return $this->markTestSkipped('Registration support is enabled.');
30 | }
31 |
32 | $response = $this->get('/register');
33 |
34 | $response->assertStatus(404);
35 | }
36 |
37 | public function test_new_users_can_register()
38 | {
39 | if (! Features::enabled(Features::registration())) {
40 | return $this->markTestSkipped('Registration support is not enabled.');
41 | }
42 |
43 | $response = $this->post('/register', [
44 | 'name' => 'Test User',
45 | 'email' => 'test@example.com',
46 | 'password' => 'password',
47 | 'password_confirmation' => 'password',
48 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(),
49 | ]);
50 |
51 | $this->assertAuthenticated();
52 | $response->assertRedirect(RouteServiceProvider::HOME);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/config/hashing.php:
--------------------------------------------------------------------------------
1 | 'bcrypt',
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Bcrypt Options
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may specify the configuration options that should be used when
26 | | passwords are hashed using the Bcrypt algorithm. This will allow you
27 | | to control the amount of time it takes to hash the given password.
28 | |
29 | */
30 |
31 | 'bcrypt' => [
32 | 'rounds' => env('BCRYPT_ROUNDS', 10),
33 | ],
34 |
35 | /*
36 | |--------------------------------------------------------------------------
37 | | Argon Options
38 | |--------------------------------------------------------------------------
39 | |
40 | | Here you may specify the configuration options that should be used when
41 | | passwords are hashed using the Argon algorithm. These will allow you
42 | | to control the amount of time it takes to hash the given password.
43 | |
44 | */
45 |
46 | 'argon' => [
47 | 'memory' => 65536,
48 | 'threads' => 1,
49 | 'time' => 4,
50 | ],
51 |
52 | ];
53 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_URL=http://localhost
6 |
7 | LOG_CHANNEL=stack
8 | LOG_DEPRECATIONS_CHANNEL=null
9 | LOG_LEVEL=debug
10 |
11 | DB_CONNECTION=mysql
12 | DB_HOST=127.0.0.1
13 | DB_PORT=3306
14 | DB_DATABASE=crmbuilder
15 | DB_USERNAME=root
16 | DB_PASSWORD=
17 |
18 | DB_T_CONNECTION=tenant22
19 | DB_HOST2=127.0.0.2
20 | DB_PORT2=3306
21 | DB_ICEBURG_DATABASE=iceburg2
22 | DB_USERNAME2=root
23 | DB_PASSWORD2=
24 |
25 | STRIPE_KEY=
26 | STRIPE_SECRET=
27 | STRIPE_WEBHOOK_SECRET=
28 |
29 | STRIPE_KEY_STAGING=
30 | STRIPE_SECRET_STAGING=
31 | STRIPE_WEBHOOK_SECRET_STAGING=
32 |
33 | GITHUB_CLIENT_ID=
34 | GITHUB_CLIENT_SECRET=
35 | GOOGLE_CLIENT_ID=
36 | GOOGLE_CLIENT_SECRET=
37 | TWITTER_CLIENT_ID=
38 | TWITTER_CLIENT_SECRET=
39 |
40 |
41 | BROADCAST_DRIVER=log
42 | CACHE_DRIVER=file
43 | FILESYSTEM_DISK=local
44 | QUEUE_CONNECTION=sync
45 | SESSION_DRIVER=database
46 | SESSION_LIFETIME=120
47 |
48 | MEMCACHED_HOST=127.0.0.1
49 |
50 | REDIS_HOST=127.0.0.1
51 | REDIS_PASSWORD=null
52 | REDIS_PORT=6379
53 |
54 | MAIL_MAILER=smtp
55 | MAIL_HOST=mailhog
56 | MAIL_PORT=1025
57 | MAIL_USERNAME=null
58 | MAIL_PASSWORD=null
59 | MAIL_ENCRYPTION=null
60 | MAIL_FROM_ADDRESS="hello@example.com"
61 | MAIL_FROM_NAME="${APP_NAME}"
62 |
63 | AWS_ACCESS_KEY_ID=
64 | AWS_SECRET_ACCESS_KEY=
65 | AWS_DEFAULT_REGION=us-east-1
66 | AWS_BUCKET=
67 | AWS_USE_PATH_STYLE_ENDPOINT=false
68 |
69 | PUSHER_APP_ID=
70 | PUSHER_APP_KEY=
71 | PUSHER_APP_SECRET=
72 | PUSHER_HOST=
73 | PUSHER_PORT=443
74 | PUSHER_SCHEME=https
75 | PUSHER_APP_CLUSTER=mt1
76 |
77 | VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
78 | VITE_PUSHER_HOST="${PUSHER_HOST}"
79 | VITE_PUSHER_PORT="${PUSHER_PORT}"
80 | VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
81 | VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
82 |
--------------------------------------------------------------------------------
/tests/Feature/InviteTeamMemberTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Team invitations not enabled.');
20 | }
21 |
22 | Mail::fake();
23 |
24 | $this->actingAs($user = User::factory()->withPersonalTeam()->create());
25 |
26 | $response = $this->post('/teams/'.$user->currentTeam->id.'/members', [
27 | 'email' => 'test@example.com',
28 | 'role' => 'admin',
29 | ]);
30 |
31 | Mail::assertSent(TeamInvitation::class);
32 |
33 | $this->assertCount(1, $user->currentTeam->fresh()->teamInvitations);
34 | }
35 |
36 | public function test_team_member_invitations_can_be_cancelled()
37 | {
38 | if (! Features::sendsTeamInvitations()) {
39 | return $this->markTestSkipped('Team invitations not enabled.');
40 | }
41 |
42 | Mail::fake();
43 |
44 | $this->actingAs($user = User::factory()->withPersonalTeam()->create());
45 |
46 | $invitation = $user->currentTeam->teamInvitations()->create([
47 | 'email' => 'test@example.com',
48 | 'role' => 'admin',
49 | ]);
50 |
51 | $response = $this->delete('/team-invitations/'.$invitation->id);
52 |
53 | $this->assertCount(0, $user->currentTeam->fresh()->teamInvitations);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Feature/UpdatePasswordTest.php:
--------------------------------------------------------------------------------
1 | actingAs($user = User::factory()->create());
17 |
18 | $response = $this->put('/user/password', [
19 | 'current_password' => 'password',
20 | 'password' => 'new-password',
21 | 'password_confirmation' => 'new-password',
22 | ]);
23 |
24 | $this->assertTrue(Hash::check('new-password', $user->fresh()->password));
25 | }
26 |
27 | public function test_current_password_must_be_correct()
28 | {
29 | $this->actingAs($user = User::factory()->create());
30 |
31 | $response = $this->put('/user/password', [
32 | 'current_password' => 'wrong-password',
33 | 'password' => 'new-password',
34 | 'password_confirmation' => 'new-password',
35 | ]);
36 |
37 | $response->assertSessionHasErrors();
38 |
39 | $this->assertTrue(Hash::check('password', $user->fresh()->password));
40 | }
41 |
42 | public function test_new_passwords_must_match()
43 | {
44 | $this->actingAs($user = User::factory()->create());
45 |
46 | $response = $this->put('/user/password', [
47 | 'current_password' => 'password',
48 | 'password' => 'new-password',
49 | 'password_confirmation' => 'wrong-password',
50 | ]);
51 |
52 | $response->assertSessionHasErrors();
53 |
54 | $this->assertTrue(Hash::check('password', $user->fresh()->password));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/Actions/Fortify/CreateNewUser.php:
--------------------------------------------------------------------------------
1 | ['required', 'string', 'max:255'],
26 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
27 | 'password' => $this->passwordRules(),
28 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
29 | ])->validate();
30 |
31 | return DB::transaction(function () use ($input) {
32 | return tap(User::create([
33 | 'name' => $input['name'],
34 | 'email' => $input['email'],
35 | 'signup_plan' => $input['signup_plan'],
36 | 'password' => Hash::make($input['password']),
37 | ]), function (User $user) {
38 | $this->createTeam($user);
39 | });
40 | });
41 | }
42 |
43 | /**
44 | * Create a personal team for the user.
45 | *
46 | * @return void
47 | */
48 | protected function createTeam(User $user)
49 | {
50 | $user->ownedTeams()->save(Team::forceCreate([
51 | 'user_id' => $user->id,
52 | 'name' => explode(' ', $user->name, 2)[0]."'s Team",
53 | 'personal_team' => true,
54 | ]));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | make(Illuminate\Contracts\Console\Kernel::class);
34 |
35 | $status = $kernel->handle(
36 | $input = new Symfony\Component\Console\Input\ArgvInput,
37 | new Symfony\Component\Console\Output\ConsoleOutput
38 | );
39 |
40 | /*
41 | |--------------------------------------------------------------------------
42 | | Shutdown The Application
43 | |--------------------------------------------------------------------------
44 | |
45 | | Once Artisan has finished running, we will fire off the shutdown events
46 | | so that any final work may be done by the application before we shut
47 | | down the process. This is the last thing to happen to the request.
48 | |
49 | */
50 |
51 | $kernel->terminate($input, $status);
52 |
53 | exit($status);
54 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/confirms-password.blade.php:
--------------------------------------------------------------------------------
1 | @props(['title' => __('Confirm Password'), 'content' => __('For your security, please confirm your password to continue.'), 'button' => __('Confirm')])
2 |
3 | @php
4 | $confirmableId = md5($attributes->wire('then'));
5 | @endphp
6 |
7 | wire('then') }}
9 | x-data
10 | x-ref="span"
11 | x-on:click="$wire.startConfirmingPassword('{{ $confirmableId }}')"
12 | x-on:password-confirmed.window="setTimeout(() => $event.detail.id === '{{ $confirmableId }}' && $refs.span.dispatchEvent(new CustomEvent('then', { bubbles: false })), 250);"
13 | >
14 | {{ $slot }}
15 |
16 |
17 | @once
18 |
19 |
20 | {{ $title }}
21 |
22 |
23 |
24 | {{ $content }}
25 |
26 |
27 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | {{ __('Cancel') }}
39 |
40 |
41 |
42 | {{ $button }}
43 |
44 |
45 |
46 | @endonce
47 |
--------------------------------------------------------------------------------
/app/Models/Iceburg/Permission.php:
--------------------------------------------------------------------------------
1 | hasOne(Module::class, 'id', 'module_id')
22 | ->where('status', 1);
23 | }
24 |
25 | public function roles()
26 | {
27 | return $this->hasOne(Role::class, 'id', 'role_id');
28 | }
29 |
30 | public static function checkPermission($module_id = 0, $type = 'read', $message = '')
31 | {
32 |
33 | if (! in_array($type, self::$types)) {
34 | return false;
35 | }
36 | if (
37 | Auth::user()->role != 'Admin'
38 | && Module::where('id', $module_id)->value('admin')
39 | ) {
40 | return false;
41 | }
42 | Logs::insert([
43 | 'user_id' => Auth::user()->id,
44 | 'type' => $type,
45 | 'message' => $message,
46 | 'module_id' => $module_id,
47 | 'created_at' => Carbon::now(),
48 | 'updated_at' => Carbon::now(),
49 | ]);
50 |
51 | return self::where('module_id', $module_id)
52 | ->where('role_id', Auth::user()->role_id)
53 | ->where('can_'.$type, 1)->value('id');
54 | }
55 |
56 | public static function getPermissions($moduleId)
57 | {
58 | $permissions = [];
59 | foreach (self::$types as $type) {
60 | $permissions[$type] = self::checkPermission($moduleId, $type);
61 | }
62 |
63 | return $permissions;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class);
50 |
51 | $response = $kernel->handle(
52 | $request = Request::capture()
53 | )->send();
54 |
55 | $kernel->terminate($request, $response);
56 |
--------------------------------------------------------------------------------
/app/Actions/Fortify/UpdateUserProfileInformation.php:
--------------------------------------------------------------------------------
1 | ['required', 'string', 'max:255'],
22 | 'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
23 | 'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'],
24 | ])->validateWithBag('updateProfileInformation');
25 |
26 | if (isset($input['photo'])) {
27 | $user->updateProfilePhoto($input['photo']);
28 | }
29 |
30 | if ($input['email'] !== $user->email &&
31 | $user instanceof MustVerifyEmail) {
32 | $this->updateVerifiedUser($user, $input);
33 | } else {
34 | $user->forceFill([
35 | 'name' => $input['name'],
36 | 'email' => $input['email'],
37 | ])->save();
38 | }
39 | }
40 |
41 | /**
42 | * Update the given verified user's profile information.
43 | *
44 | * @param mixed $user
45 | * @return void
46 | */
47 | protected function updateVerifiedUser($user, array $input)
48 | {
49 | $user->forceFill([
50 | 'name' => $input['name'],
51 | 'email' => $input['email'],
52 | 'email_verified_at' => null,
53 | ])->save();
54 |
55 | $user->sendEmailVerificationNotification();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/resources/views/vendor/jetstream/components/modal.blade.php:
--------------------------------------------------------------------------------
1 | @props(['id', 'maxWidth'])
2 |
3 | @php
4 | $id = $id ?? md5($attributes->wire('model'));
5 |
6 | $maxWidth = [
7 | 'sm' => 'sm:max-w-sm',
8 | 'md' => 'sm:max-w-md',
9 | 'lg' => 'sm:max-w-lg',
10 | 'xl' => 'sm:max-w-xl',
11 | '2xl' => 'sm:max-w-2xl',
12 | ][$maxWidth ?? '2xl'];
13 | @endphp
14 |
15 |
24 |
32 |
33 |
41 | {{ $slot }}
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/Actions/Jetstream/RemoveTeamMember.php:
--------------------------------------------------------------------------------
1 | authorize($user, $team, $teamMember);
24 |
25 | $this->ensureUserDoesNotOwnTeam($teamMember, $team);
26 |
27 | $team->removeUser($teamMember);
28 |
29 | TeamMemberRemoved::dispatch($team, $teamMember);
30 | }
31 |
32 | /**
33 | * Authorize that the user can remove the team member.
34 | *
35 | * @param mixed $user
36 | * @param mixed $team
37 | * @param mixed $teamMember
38 | * @return void
39 | */
40 | protected function authorize($user, $team, $teamMember)
41 | {
42 | if (! Gate::forUser($user)->check('removeTeamMember', $team) &&
43 | $user->id !== $teamMember->id) {
44 | throw new AuthorizationException;
45 | }
46 | }
47 |
48 | /**
49 | * Ensure that the currently authenticated user does not own the team.
50 | *
51 | * @param mixed $teamMember
52 | * @param mixed $team
53 | * @return void
54 | */
55 | protected function ensureUserDoesNotOwnTeam($teamMember, $team)
56 | {
57 | if ($teamMember->id === $team->owner->id) {
58 | throw ValidationException::withMessages([
59 | 'team' => [__('You may not leave a team that you created.')],
60 | ])->errorBag('removeTeamMember');
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 | $this->faker->name(),
29 | 'email' => $this->faker->unique()->safeEmail(),
30 | 'email_verified_at' => now(),
31 | 'plan_id' => 1,
32 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
33 | 'remember_token' => Str::random(10),
34 | ];
35 | }
36 |
37 | /**
38 | * Indicate that the model's email address should be unverified.
39 | *
40 | * @return \Illuminate\Database\Eloquent\Factories\Factory
41 | */
42 | public function unverified()
43 | {
44 | return $this->state(function (array $attributes) {
45 | return [
46 | 'email_verified_at' => null,
47 | ];
48 | });
49 | }
50 |
51 | /**
52 | * Indicate that the user should have a personal team.
53 | *
54 | * @return $this
55 | */
56 | public function withPersonalTeam()
57 | {
58 | if (! Features::hasTeamFeatures()) {
59 | return $this->state([]);
60 | }
61 |
62 | return $this->has(
63 | Team::factory()
64 | ->state(function (array $attributes, User $user) {
65 | return ['name' => $user->name.'\'s Team', 'user_id' => $user->id, 'personal_team' => true];
66 | }),
67 | 'ownedTeams'
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | IceburgCRM SaaS
2 | ===============
3 |
4 | This is the repository for the IceburgCRM SaaS project, which powers [IceburgCRM.com](https://www.iceburgcrm.com/) and allows customers to host their own IceburgCRMs.
5 |
6 | Screenshots:
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | [Demo](https://www.iceburgcrm.com)
26 |
27 | Features
28 | --------
29 |
30 | - Host customer's IceburgCRMs
31 | - Built with Laravel JetStream, Socialite, Tailwinds, and DaisyUI
32 | - Integrates with Stripe for payment processing
33 | - Allows users to authenticate with GitHub using Socialite
34 |
35 | Requirements
36 | ------------
37 |
38 | - PHP >= 8.1
39 | - Composer
40 | - MySQL >= 5.7 or MariaDB >= 10.2
41 | - Stripe API key
42 | - GitHub OAuth key for Socialite
43 |
44 | Installation
45 | ------------
46 |
47 | 1. Clone this repository.
48 | 2. Copy `.env.example` to `.env` and configure the database settings, Stripe API key, and GitHub OAuth key for Socialite.
49 | 3. Run `composer install` to install the PHP dependencies.
50 | 4. Run `npm install` to install the Node.js dependencies.
51 | 5. Run `php artisan key:generate` to generate an application key.
52 | 6. Run `php artisan migrate` to run the database migrations.
53 | 7. Run `npm run dev` to compile the assets.
54 | 8. Serve the application with `php artisan serve` or use a web server like Apache or Nginx.
55 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ForgotPassword.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.
33 |
34 |
35 |
36 |
37 | {{ status }}
38 |
39 |
40 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/Policies/TeamPolicy.php:
--------------------------------------------------------------------------------
1 | belongsToTeam($team);
31 | }
32 |
33 | /**
34 | * Determine whether the user can create models.
35 | *
36 | * @return mixed
37 | */
38 | public function create(User $user)
39 | {
40 | return true;
41 | }
42 |
43 | /**
44 | * Determine whether the user can update the model.
45 | *
46 | * @return mixed
47 | */
48 | public function update(User $user, Team $team)
49 | {
50 | return $user->ownsTeam($team);
51 | }
52 |
53 | /**
54 | * Determine whether the user can add team members.
55 | *
56 | * @return mixed
57 | */
58 | public function addTeamMember(User $user, Team $team)
59 | {
60 | return $user->ownsTeam($team);
61 | }
62 |
63 | /**
64 | * Determine whether the user can update team member permissions.
65 | *
66 | * @return mixed
67 | */
68 | public function updateTeamMember(User $user, Team $team)
69 | {
70 | return $user->ownsTeam($team);
71 | }
72 |
73 | /**
74 | * Determine whether the user can remove team members.
75 | *
76 | * @return mixed
77 | */
78 | public function removeTeamMember(User $user, Team $team)
79 | {
80 | return $user->ownsTeam($team);
81 | }
82 |
83 | /**
84 | * Determine whether the user can delete the model.
85 | *
86 | * @return mixed
87 | */
88 | public function delete(User $user, Team $team)
89 | {
90 | return $user->ownsTeam($team);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/resources/js/Components/ConfirmationModal.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
33 |
34 |
35 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ConfirmPassword.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | This is a secure area of the application. Please confirm your password before continuing.
38 |
39 |
40 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/public/dist/assets/logos/Sun.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Show.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 | Profile
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------