├── database
├── .gitignore
├── seeders
│ ├── ProductionSeeder.php
│ ├── DatabaseSeeder.php
│ ├── UserSeeder.php
│ └── TodoSeeder.php
├── settings
│ ├── 2024_12_29_210810_add_self_hosted.php
│ └── 2024_12_28_210810_create_instance_settings.php
├── migrations
│ ├── 2022_12_14_083707_create_settings_table.php
│ ├── 2025_01_15_090304_create_hashtags_table.php
│ ├── 2025_01_15_090306_create_hashtag_todo_table.php
│ ├── 2024_03_14_000000_create_purchases_table.php
│ ├── 2025_01_14_090304_create_todos_table.php
│ ├── 0001_01_01_000001_create_cache_table.php
│ ├── 2025_01_08_080321_create_subscription_items_table.php
│ ├── 2025_01_08_080321_create_customer_columns.php
│ ├── 2025_01_08_080322_create_subscriptions_table.php
│ ├── 2024_11_10_115838_add_two_factor_columns_to_users_table.php
│ ├── 0001_01_01_000000_create_users_table.php
│ └── 0001_01_01_000002_create_jobs_table.php
└── factories
│ └── UserFactory.php
├── bootstrap
├── cache
│ └── .gitignore
├── providers.php
└── app.php
├── storage
├── logs
│ └── .gitignore
├── app
│ ├── private
│ │ └── .gitignore
│ ├── public
│ │ └── .gitignore
│ └── .gitignore
└── framework
│ ├── testing
│ └── .gitignore
│ ├── views
│ └── .gitignore
│ ├── cache
│ ├── data
│ │ └── .gitignore
│ └── .gitignore
│ ├── sessions
│ └── .gitignore
│ └── .gitignore
├── public
├── robots.txt
├── jata.png
├── og-image.png
├── vendor
│ └── log-viewer
│ │ ├── img
│ │ ├── log-viewer-32.png
│ │ ├── log-viewer-64.png
│ │ └── log-viewer-128.png
│ │ ├── mix-manifest.json
│ │ └── app.js.LICENSE.txt
├── images
│ └── icons
│ │ ├── android-launchericon-48-48.png
│ │ ├── android-launchericon-72-72.png
│ │ ├── android-launchericon-96-96.png
│ │ ├── android-launchericon-144-144.png
│ │ ├── android-launchericon-192-192.png
│ │ └── android-launchericon-512-512.png
├── index.php
├── .htaccess
└── serviceworker.js
├── docker
├── production
│ ├── etc
│ │ └── s6-overlay
│ │ │ └── s6-rc.d
│ │ │ ├── user
│ │ │ └── contents.d
│ │ │ │ ├── reverb
│ │ │ │ ├── horizon
│ │ │ │ ├── init-script
│ │ │ │ ├── init-seeder
│ │ │ │ └── scheduler
│ │ │ ├── horizon
│ │ │ ├── type
│ │ │ ├── dependencies.d
│ │ │ │ └── init-script
│ │ │ └── run
│ │ │ ├── reverb
│ │ │ ├── type
│ │ │ ├── dependencies.d
│ │ │ │ └── init-script
│ │ │ └── run
│ │ │ ├── scheduler
│ │ │ ├── type
│ │ │ ├── dependencies.d
│ │ │ │ └── init-script
│ │ │ └── run
│ │ │ ├── init-script
│ │ │ ├── type
│ │ │ └── up
│ │ │ └── init-seeder
│ │ │ ├── type
│ │ │ ├── dependecies.d
│ │ │ └── init-script
│ │ │ └── up
│ ├── nginx.conf
│ └── Dockerfile
└── development
│ ├── etc
│ └── s6-overlay
│ │ └── s6-rc.d
│ │ ├── horizon
│ │ ├── type
│ │ ├── dependencies.d
│ │ │ └── init-script
│ │ └── run
│ │ ├── migration
│ │ ├── type
│ │ ├── dependecies.d
│ │ │ └── init-script
│ │ └── up
│ │ ├── reverb
│ │ ├── type
│ │ ├── dependencies.d
│ │ │ └── init-script
│ │ └── run
│ │ ├── scheduler
│ │ ├── type
│ │ ├── dependencies.d
│ │ │ └── init-script
│ │ └── run
│ │ ├── user
│ │ └── contents.d
│ │ │ ├── horizon
│ │ │ ├── migration
│ │ │ ├── reverb
│ │ │ ├── scheduler
│ │ │ ├── init-script
│ │ │ └── init-seeder
│ │ ├── init-script
│ │ ├── type
│ │ └── up
│ │ └── init-seeder
│ │ ├── type
│ │ ├── dependecies.d
│ │ └── migration
│ │ └── up
│ ├── entrypoint.d
│ └── 99-compose-install.sh
│ ├── Dockerfile
│ └── nginx.conf
├── resources
├── views
│ ├── components
│ │ ├── tooltip
│ │ │ ├── index.blade.php
│ │ │ ├── trigger.blade.php
│ │ │ └── content.blade.php
│ │ ├── breadcrumb
│ │ │ ├── index.blade.php
│ │ │ ├── item.blade.php
│ │ │ ├── link.blade.php
│ │ │ ├── list.blade.php
│ │ │ ├── page.blade.php
│ │ │ ├── ellipsis.blade.php
│ │ │ └── separator.blade.php
│ │ ├── sheet
│ │ │ ├── trigger.blade.php
│ │ │ ├── main.blade.php
│ │ │ ├── description.blade.php
│ │ │ ├── title.blade.php
│ │ │ ├── header.blade.php
│ │ │ ├── footer.blade.php
│ │ │ ├── close.blade.php
│ │ │ ├── index.blade.php
│ │ │ ├── overlay.blade.php
│ │ │ └── content.blade.php
│ │ ├── form
│ │ │ ├── label.blade.php
│ │ │ ├── index.blade.php
│ │ │ ├── description.blade.php
│ │ │ ├── item.blade.php
│ │ │ ├── message.blade.php
│ │ │ └── input.blade.php
│ │ ├── popover
│ │ │ ├── close.blade.php
│ │ │ ├── trigger.blade.php
│ │ │ ├── index.blade.php
│ │ │ └── content.blade.php
│ │ ├── tabs
│ │ │ ├── index.blade.php
│ │ │ ├── list.blade.php
│ │ │ ├── content.blade.php
│ │ │ └── trigger.blade.php
│ │ ├── alert
│ │ │ ├── description.blade.php
│ │ │ ├── title.blade.php
│ │ │ └── index.blade.php
│ │ ├── card
│ │ │ ├── content.blade.php
│ │ │ ├── footer.blade.php
│ │ │ ├── description.blade.php
│ │ │ ├── header.blade.php
│ │ │ ├── title.blade.php
│ │ │ └── index.blade.php
│ │ ├── dropdown-menu
│ │ │ ├── close.blade.php
│ │ │ ├── group.blade.php
│ │ │ ├── separator.blade.php
│ │ │ ├── shortcut.blade.php
│ │ │ ├── trigger.blade.php
│ │ │ ├── index.blade.php
│ │ │ ├── label.blade.php
│ │ │ ├── sub
│ │ │ │ ├── index.blade.php
│ │ │ │ ├── content.blade.php
│ │ │ │ └── trigger.blade.php
│ │ │ ├── radioitemgroup.blade.php
│ │ │ ├── content.blade.php
│ │ │ ├── item.blade.php
│ │ │ ├── checkboxitem.blade.php
│ │ │ └── radioitem.blade.php
│ │ ├── hover-card
│ │ │ ├── index.blade.php
│ │ │ ├── trigger.blade.php
│ │ │ └── content.blade.php
│ │ ├── link
│ │ │ └── index.blade.php
│ │ ├── typography
│ │ │ ├── large
│ │ │ │ └── index.blade.php
│ │ │ ├── lead
│ │ │ │ └── index.blade.php
│ │ │ ├── list
│ │ │ │ └── index.blade.php
│ │ │ ├── muted
│ │ │ │ └── index.blade.php
│ │ │ ├── p
│ │ │ │ └── index.blade.php
│ │ │ ├── small
│ │ │ │ └── index.blade.php
│ │ │ ├── blockquote
│ │ │ │ └── index.blade.php
│ │ │ ├── h3
│ │ │ │ └── index.blade.php
│ │ │ ├── h4
│ │ │ │ └── index.blade.php
│ │ │ ├── h1
│ │ │ │ └── index.blade.php
│ │ │ ├── h2
│ │ │ │ └── index.blade.php
│ │ │ └── inline-code
│ │ │ │ └── index.blade.php
│ │ ├── dialog
│ │ │ ├── description.blade.php
│ │ │ ├── header.blade.php
│ │ │ ├── title.blade.php
│ │ │ ├── footer.blade.php
│ │ │ ├── index.blade.php
│ │ │ ├── trigger.blade.php
│ │ │ ├── close.blade.php
│ │ │ ├── save.blade.php
│ │ │ └── content.blade.php
│ │ ├── accordion
│ │ │ ├── index.blade.php
│ │ │ ├── item.blade.php
│ │ │ ├── content.blade.php
│ │ │ └── trigger.blade.php
│ │ ├── checkbox
│ │ │ └── index.blade.php
│ │ ├── avatar
│ │ │ ├── fallback.blade.php
│ │ │ ├── image.blade.php
│ │ │ └── index.blade.php
│ │ ├── switch
│ │ │ ├── thumb.blade.php
│ │ │ └── index.blade.php
│ │ ├── badge
│ │ │ └── index.blade.php
│ │ ├── label
│ │ │ └── index.blade.php
│ │ ├── radio-group
│ │ │ ├── item.blade.php
│ │ │ └── index.blade.php
│ │ ├── select
│ │ │ └── index.blade.php
│ │ ├── separator
│ │ │ └── index.blade.php
│ │ ├── button
│ │ │ └── index.blade.php
│ │ ├── aside.blade.php
│ │ ├── textarea
│ │ │ └── index.blade.php
│ │ ├── navigation.blade.php
│ │ └── input
│ │ │ └── index.blade.php
│ ├── vendor
│ │ ├── laravelpwa
│ │ │ ├── offline.blade.php
│ │ │ └── meta.blade.php
│ │ └── tall-toasts
│ │ │ ├── includes
│ │ │ ├── content.blade.php
│ │ │ └── icon.blade.php
│ │ │ └── livewire
│ │ │ └── toasts.blade.php
│ └── livewire
│ │ ├── forms
│ │ ├── hashtag-list.blade.php
│ │ └── todo-input.blade.php
│ │ ├── auth
│ │ ├── login.blade.php
│ │ └── register.blade.php
│ │ ├── billing.blade.php
│ │ └── dump.blade.php
└── js
│ ├── bootstrap.js
│ ├── echo.js
│ ├── app.js
│ └── plugins
│ ├── tooltip.js
│ ├── hover-card.js
│ ├── accordion.js
│ └── form.js
├── tests
├── Unit
│ └── ExampleTest.php
├── Feature
│ └── ExampleTest.php
├── TestCase.php
└── Pest.php
├── postcss.config.js
├── app
├── Http
│ ├── Controllers
│ │ └── Controller.php
│ └── Middleware
│ │ ├── EnsureInstanceAdmin.php
│ │ ├── EnsurePaymentRoutesRegistered.php
│ │ ├── EnsureSubscription.php
│ │ └── HandleInertiaRequests.php
├── Actions
│ └── Fortify
│ │ ├── PasswordValidationRules.php
│ │ ├── ResetUserPassword.php
│ │ ├── UpdateUserPassword.php
│ │ ├── CreateNewUser.php
│ │ └── UpdateUserProfileInformation.php
├── View
│ └── Components
│ │ ├── Aside.php
│ │ ├── Logo.php
│ │ └── Navigation.php
├── Console
│ └── Commands
│ │ ├── StartHorizon.php
│ │ ├── StartScheduler.php
│ │ ├── StartMigration.php
│ │ ├── StartSeeder.php
│ │ └── Init.php
├── Models
│ ├── Purchase.php
│ └── Hashtag.php
├── Events
│ └── TodoUpdated.php
├── Services
│ ├── AlertCvaService.php
│ ├── BadgeCvaService.php
│ ├── ButtonCvaService.php
│ └── DialogCvaService.php
├── Providers
│ ├── HorizonServiceProvider.php
│ ├── AppServiceProvider.php
│ └── FortifyServiceProvider.php
├── Settings
│ └── InstanceSettings.php
└── Livewire
│ ├── Billing.php
│ ├── Forms
│ ├── HashtagList.php
│ └── TodoInput.php
│ ├── Auth
│ ├── Login.php
│ └── Register.php
│ └── Dump.php
├── docker-compose.yml
├── routes
├── api.php
├── channels.php
└── console.php
├── .gitattributes
├── README.md
├── .editorconfig
├── .env.production.example
├── .env.development.composer.example
├── .env.development.spin.example
├── .gitignore
├── .dockerignore
├── artisan
├── config
├── tall-toasts.php
├── constants.php
├── services.php
├── laravelpwa.php
├── tailwind-merge.php
├── filesystems.php
├── broadcasting.php
├── settings.php
├── reverb.php
└── cache.php
├── package.json
├── docker-compose.dev.yml
├── vite.config.js
├── scripts
└── production-build-locally.sh
├── phpunit.xml
├── composer.json
└── tailwind.config.js
/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/private/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/testing/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/reverb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/storage/framework/cache/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/sessions/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/horizon/type:
--------------------------------------------------------------------------------
1 | longrun
2 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/migration/type:
--------------------------------------------------------------------------------
1 | oneshot
2 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/reverb/type:
--------------------------------------------------------------------------------
1 | longrun
2 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/scheduler/type:
--------------------------------------------------------------------------------
1 | longrun
2 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/horizon:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/migration:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/reverb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/horizon/type:
--------------------------------------------------------------------------------
1 | longrun
2 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/reverb/type:
--------------------------------------------------------------------------------
1 | longrun
2 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/scheduler/type:
--------------------------------------------------------------------------------
1 | longrun
2 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/horizon:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/init-script:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/init-seeder:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !data/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/init-script/type:
--------------------------------------------------------------------------------
1 | oneshot
2 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/init-seeder/type:
--------------------------------------------------------------------------------
1 | oneshot
2 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/init-script:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/init-seeder:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/init-script/type:
--------------------------------------------------------------------------------
1 | oneshot
2 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/init-seeder/type:
--------------------------------------------------------------------------------
1 | oneshot
2 |
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !private/
3 | !public/
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-script:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/init-seeder/dependecies.d/migration:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/migration/dependecies.d/init-script:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/reverb/dependencies.d/init-script:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/scheduler/dependencies.d/init-script:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-script:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/init-seeder/dependecies.d/init-script:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/reverb/dependencies.d/init-script:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/scheduler/dependencies.d/init-script:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/jata.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coollabsio/jata/HEAD/public/jata.png
--------------------------------------------------------------------------------
/public/og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coollabsio/jata/HEAD/public/og-image.png
--------------------------------------------------------------------------------
/resources/views/components/tooltip/index.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/breadcrumb/index.blade.php:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/init-script/up:
--------------------------------------------------------------------------------
1 | #!/command/execlineb -P
2 | php /var/www/html/artisan app:init
3 |
--------------------------------------------------------------------------------
/resources/views/components/sheet/trigger.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/form/label.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/popover/close.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/tabs/index.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/tests/Unit/ExampleTest.php:
--------------------------------------------------------------------------------
1 | toBeTrue();
5 | });
6 |
--------------------------------------------------------------------------------
/resources/views/components/alert/description.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/card/content.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('p-6 pt-0') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/close.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/sheet/main.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('row-start-2') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/init-script/up:
--------------------------------------------------------------------------------
1 | #!/command/execlineb -P
2 | php /var/www/html/artisan app:init --production
3 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/resources/views/components/hover-card/index.blade.php:
--------------------------------------------------------------------------------
1 |
5 | {{ $slot }}
6 |
7 |
--------------------------------------------------------------------------------
/public/vendor/log-viewer/img/log-viewer-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coollabsio/jata/HEAD/public/vendor/log-viewer/img/log-viewer-32.png
--------------------------------------------------------------------------------
/public/vendor/log-viewer/img/log-viewer-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coollabsio/jata/HEAD/public/vendor/log-viewer/img/log-viewer-64.png
--------------------------------------------------------------------------------
/resources/views/components/alert/title.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/link/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('font-medium underline underline-offset-4') }}>{{ $slot }}
2 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | twMerge('flex items-center p-6 pt-0') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/group.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/public/images/icons/android-launchericon-48-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coollabsio/jata/HEAD/public/images/icons/android-launchericon-48-48.png
--------------------------------------------------------------------------------
/public/images/icons/android-launchericon-72-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coollabsio/jata/HEAD/public/images/icons/android-launchericon-72-72.png
--------------------------------------------------------------------------------
/public/images/icons/android-launchericon-96-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coollabsio/jata/HEAD/public/images/icons/android-launchericon-96-96.png
--------------------------------------------------------------------------------
/resources/views/components/card/description.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('text-sm text-muted-foreground') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/card/header.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('flex flex-col space-y-1.5 p-6') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/separator.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/shortcut.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/trigger.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/tooltip/trigger.blade.php:
--------------------------------------------------------------------------------
1 |
5 | {{ $slot }}
6 |
7 |
--------------------------------------------------------------------------------
/resources/views/components/typography/large/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('text-lg font-semibold') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/public/images/icons/android-launchericon-144-144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coollabsio/jata/HEAD/public/images/icons/android-launchericon-144-144.png
--------------------------------------------------------------------------------
/public/images/icons/android-launchericon-192-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coollabsio/jata/HEAD/public/images/icons/android-launchericon-192-192.png
--------------------------------------------------------------------------------
/public/images/icons/android-launchericon-512-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coollabsio/jata/HEAD/public/images/icons/android-launchericon-512-512.png
--------------------------------------------------------------------------------
/resources/views/components/card/title.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('font-semibold leading-none tracking-tight') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/dialog/description.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/dialog/header.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('flex flex-col space-y-1.5 text-left') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/hover-card/trigger.blade.php:
--------------------------------------------------------------------------------
1 |
5 | {{ $slot }}
6 |
7 |
--------------------------------------------------------------------------------
/resources/views/components/sheet/description.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('text-sm text-muted-foreground') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/sheet/title.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('text-lg font-semibold text-foreground') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/typography/lead/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('text-xl text-muted-foreground') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/typography/list/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('my-6 ml-6 list-disc space-y-2') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/typography/muted/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('text-sm text-muted-foreground') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/accordion/index.blade.php:
--------------------------------------------------------------------------------
1 |
6 | {{ $slot }}
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/components/breadcrumb/item.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('inline-flex items-center gap-1.5') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/breadcrumb/link.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('transition-colors hover:text-foreground select-none') }}>{{ $slot }}
2 |
--------------------------------------------------------------------------------
/resources/views/components/typography/p/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('leading-7 [&:not(:first-child)]:mt-6') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | window.axios = axios;
3 |
4 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
5 |
--------------------------------------------------------------------------------
/resources/views/components/typography/small/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('text-sm font-medium leading-none') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | php:
3 | restart: always
4 | working_dir: /var/www/html
5 | extra_hosts:
6 | - host.docker.internal:host-gateway
7 |
--------------------------------------------------------------------------------
/resources/views/components/card/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('rounded-xl border bg-card text-card-foreground shadow') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/form/index.blade.php:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/resources/views/components/typography/blockquote/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('mt-6 border-l-2 pl-6 italic') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/typography/h3/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('scroll-m-20 text-2xl font-semibold tracking-tight') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/typography/h4/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('scroll-m-20 text-xl font-semibold tracking-tight') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/sheet/header.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('flex flex-col space-y-2 text-center sm:text-left') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | json(['message' => 'OK']);
7 | // });
8 |
--------------------------------------------------------------------------------
/resources/views/components/dialog/title.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('text-lg font-semibold leading-none tracking-tight text-foreground') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/form/description.blade.php:
--------------------------------------------------------------------------------
1 | merge(['class' => 'text-[0.8rem] text-muted-foreground' ])}}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/typography/h1/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/tests/Feature/ExampleTest.php:
--------------------------------------------------------------------------------
1 | get('/');
5 |
6 | $response->assertStatus(200);
7 | });
8 |
--------------------------------------------------------------------------------
/storage/framework/.gitignore:
--------------------------------------------------------------------------------
1 | compiled.php
2 | config.php
3 | down
4 | events.scanned.php
5 | maintenance.php
6 | routes.php
7 | routes.scanned.php
8 | schedule-*
9 | services.json
10 |
--------------------------------------------------------------------------------
/resources/views/components/dialog/footer.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('flex flex-col sm:flex-row-reverse sm:justify-between space-y-2 sm:space-y-0') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/sheet/footer.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('row-start-3 flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/vendor/laravelpwa/offline.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 | You are currently not connected to any networks.
6 |
7 | @endsection
--------------------------------------------------------------------------------
/resources/views/components/typography/h2/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0') }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/sheet/close.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('') }}>
2 | {{ $slot }}
3 | Close
4 |
5 |
--------------------------------------------------------------------------------
/resources/views/components/sheet/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('') }}
5 | >
6 | {{ $slot }}
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/components/typography/inline-code/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold') }}>{{ $slot }}
2 |
--------------------------------------------------------------------------------
/bootstrap/providers.php:
--------------------------------------------------------------------------------
1 | twMerge('peer rounded checked:text-primary disabled:cursor-not-allowed disabled:opacity-50') }}
4 | />
5 |
--------------------------------------------------------------------------------
/resources/views/components/dialog/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('') }}
5 | >
6 | {{ $slot }}
7 |
8 |
--------------------------------------------------------------------------------
/routes/channels.php:
--------------------------------------------------------------------------------
1 | id === (int) $userId;
7 | });
8 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | twMerge('inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground') }}>
3 | {{ $slot }}
4 |
5 |
--------------------------------------------------------------------------------
/resources/views/components/breadcrumb/list.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5') }}>
3 | {{ $slot }}
4 |
5 |
--------------------------------------------------------------------------------
/resources/views/components/dialog/trigger.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'variant' => 'outline',
3 | ])
4 | twMerge() }}>
5 | {{ $slot }}
6 |
7 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/index.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $slot }}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/label.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'inset' => false,
3 | ])
4 |
5 | twMerge(['px-2 py-1.5 text-sm font-semibold', 'pl-8' => $inset]) }}>
6 | {{ $slot }}
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/components/accordion/item.blade.php:
--------------------------------------------------------------------------------
1 | @props(['value'])
2 |
3 | twMerge('border-b') }} x-data="{ item: '{{ $value }}' }" :data-state="__getDataState(item)">
4 | {{ $slot }}
5 |
6 |
--------------------------------------------------------------------------------
/resources/views/components/avatar/fallback.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('flex h-full w-full items-center justify-center rounded-full bg-muted select-none') }}>
3 | {{ $slot }}
4 |
5 |
--------------------------------------------------------------------------------
/resources/views/components/tabs/content.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'value' => '',
3 | ])
4 |
5 | twMerge('mt-2') }}
8 | >
9 | {{ $slot }}
10 |
11 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/sub/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('focus-within:bg-accent focus-within:text-accent-foreground') }}
5 | >
6 | {{ $slot }}
7 |
8 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
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 |
--------------------------------------------------------------------------------
/resources/views/components/form/item.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'name' => '',
3 | ])
4 | twMerge('space-y-2') }}
8 | >
9 | {{ $slot }}
10 |
11 |
--------------------------------------------------------------------------------
/resources/views/components/switch/thumb.blade.php:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Jata (Just Another Todo App)
2 |
3 | It is a simple todo app built for simplicity.
4 |
5 | ## Features
6 | - Daily / dump todos.
7 | - Hashtags to categorize.
8 | - Undone todos from yesterday could be moved to today.
9 | - That's it.
10 |
--------------------------------------------------------------------------------
/resources/views/components/breadcrumb/page.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('font-normal text-foreground') }}
6 | >
7 | {{ $slot }}
8 |
9 |
--------------------------------------------------------------------------------
/resources/views/components/sheet/overlay.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('fixed inset-0 z-50 bg-black/80') }}
6 | >
7 | {{ $slot }}
8 |
9 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/init-seeder/up:
--------------------------------------------------------------------------------
1 | #!/command/execlineb -P
2 |
3 | # Use with-contenv to ensure environment variables are available
4 | with-contenv
5 | cd /var/www/html
6 | foreground {
7 | php
8 | artisan
9 | start:seeder
10 | }
--------------------------------------------------------------------------------
/resources/views/components/alert/index.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'variant' => null,
3 | ])
4 |
5 | @inject('alert', 'App\Services\AlertCvaService')
6 |
7 | twMerge($alert(['variant' => $variant])) }}>
8 | {{ $slot }}
9 |
10 |
--------------------------------------------------------------------------------
/resources/views/components/badge/index.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'variant' => null,
3 | ])
4 |
5 | @inject('badge', 'App\Services\BadgeCvaService')
6 |
7 | twMerge($badge(['variant' => $variant])) }}>
8 | {{ $slot }}
9 |
10 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/migration/up:
--------------------------------------------------------------------------------
1 | #!/command/execlineb -P
2 |
3 | # Use with-contenv to ensure environment variables are available
4 | with-contenv
5 | cd /var/www/html
6 | foreground {
7 | php
8 | artisan
9 | start:migration
10 | }
--------------------------------------------------------------------------------
/database/seeders/ProductionSeeder.php:
--------------------------------------------------------------------------------
1 | comment(Inspiring::quote());
8 | // })->purpose('Display an inspiring quote')->everyMinute();
9 |
--------------------------------------------------------------------------------
/docker/development/etc/s6-overlay/s6-rc.d/reverb/run:
--------------------------------------------------------------------------------
1 | #!/command/execlineb -P
2 |
3 | # Use with-contenv to ensure environment variables are available
4 | with-contenv
5 | cd /var/www/html
6 | foreground {
7 | php
8 | artisan
9 | reverb:start --port=9999
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/docker/production/etc/s6-overlay/s6-rc.d/reverb/run:
--------------------------------------------------------------------------------
1 | #!/command/execlineb -P
2 |
3 | # Use with-contenv to ensure environment variables are available
4 | with-contenv
5 | cd /var/www/html
6 | foreground {
7 | php
8 | artisan
9 | reverb:start --port=9999
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/resources/views/components/accordion/content.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('overflow-hidden text-sm') }}
6 | >
7 |
8 | {{ $slot }}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/resources/views/components/label/index.blade.php:
--------------------------------------------------------------------------------
1 | @props(['htmlFor' => ''])
2 |
3 |
7 |
--------------------------------------------------------------------------------
/resources/views/components/breadcrumb/ellipsis.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('flex h-9 w-9 items-center justify-center') }}
5 | >
6 |
7 | More
8 |
9 |
--------------------------------------------------------------------------------
/resources/views/components/dialog/close.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'variant' => 'outline',
3 | ])
4 |
5 |
9 | @if ($slot->isEmpty())
10 | {{ __('Close') }}
11 | @else
12 | {{ $slot }}
13 | @endif
14 |
15 |
--------------------------------------------------------------------------------
/resources/views/components/avatar/image.blade.php:
--------------------------------------------------------------------------------
1 |
twMerge('aspect-square h-full w-full') }}
8 | />
9 |
10 |
--------------------------------------------------------------------------------
/resources/views/components/popover/trigger.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('inline-flex')}}
8 | >
9 | {{ $slot }}
10 |
11 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/radioitemgroup.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'defaultValue' => '',
3 | ])
4 |
5 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/resources/views/components/form/message.blade.php:
--------------------------------------------------------------------------------
1 | @aware(['name'])
2 | @props([
3 | 'name' => '',
4 | ])
5 |
6 | @error($name)
7 | merge(['class' => 'text-[0.8rem] font-medium text-destructive']) }}
10 | >
11 | {{ $message }}
12 |
13 | @enderror
14 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/resources/views/components/breadcrumb/separator.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('[&>svg]:w-3.5 [&>svg]:h-3.5') }}
5 | >
6 | @if ($slot->isEmpty())
7 |
8 | @else
9 | {{ $slot }}
10 | @endif
11 |
12 |
--------------------------------------------------------------------------------
/resources/views/components/radio-group/item.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50') }}
6 | />
7 |
--------------------------------------------------------------------------------
/.env.production.example:
--------------------------------------------------------------------------------
1 | APP_NAME=YourAppName
2 | APP_KEY=yourrandomkey
3 | APP_URL=https://yourhostname.com
4 |
5 | REVERB_APP_ID=somerandomid
6 | REVERB_APP_KEY=somerandomkey
7 | REVERB_APP_SECRET=somerandomsecret
8 | REVERB_HOST="yourhostname.com"
9 | REVERB_PORT=443
10 | REVERB_SCHEME="https"
11 |
12 | AUTORUN_ENABLED=true
13 |
14 | REDIS_URL=redis://yourredisurl
15 |
16 | PHP_OPCACHE_ENABLE=1
17 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/sub/content.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md') }}
7 | >
8 | {{ $slot }}
9 |
10 |
--------------------------------------------------------------------------------
/.env.development.composer.example:
--------------------------------------------------------------------------------
1 | APP_ENV=local
2 | APP_NAME=APP
3 | APP_KEY=base64:UIBARSL95dyvuf31yg/zoItEDaPneZtUXbGdMriQjTc=
4 | APP_DEBUG=false
5 | APP_URL=http://localhost:8000
6 |
7 | REDIS_HOST=redis
8 |
9 | REVERB_APP_ID=826633
10 | REVERB_APP_KEY=yk6grxzzsx83xl971wls
11 | REVERB_APP_SECRET=hitxqoyowvepsr2i6e7y
12 | REVERB_HOST="localhost"
13 | REVERB_PORT=8080
14 | REVERB_SCHEME="http"
15 |
16 |
--------------------------------------------------------------------------------
/.env.development.spin.example:
--------------------------------------------------------------------------------
1 | APP_ENV=local
2 | APP_NAME=APP
3 | APP_KEY=base64:UIBARSL95dyvuf31yg/zoItEDaPneZtUXbGdMriQjTc=
4 | APP_DEBUG=false
5 | APP_URL=http://localhost:8000
6 |
7 | REDIS_HOST=redis
8 |
9 | REVERB_APP_ID=826633
10 | REVERB_APP_KEY=yk6grxzzsx83xl971wls
11 | REVERB_APP_SECRET=hitxqoyowvepsr2i6e7y
12 | REVERB_HOST="localhost"
13 | REVERB_PORT=9999
14 | REVERB_SCHEME="http"
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.phpunit.cache
2 | /node_modules
3 | /public/build
4 | /public/hot
5 | /public/storage
6 | /storage/*.key
7 | /storage/pail
8 | /vendor
9 | .env
10 | .env.backup
11 | .env.production
12 | .env.development
13 | .phpactor.json
14 | .phpunit.result.cache
15 | Homestead.json
16 | Homestead.yaml
17 | auth.json
18 | npm-debug.log
19 | yarn-error.log
20 | /.fleet
21 | /.idea
22 | /.vscode
23 | /.zed
24 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | /.phpunit.cache
2 | /node_modules
3 | /public/build
4 | /public/hot
5 | /public/storage
6 | /storage/*.key
7 | /storage/pail
8 | /vendor
9 | .env
10 | .env.backup
11 | .env.production
12 | .env.development
13 | .phpactor.json
14 | .phpunit.result.cache
15 | Homestead.json
16 | Homestead.yaml
17 | auth.json
18 | npm-debug.log
19 | yarn-error.log
20 | /.fleet
21 | /.idea
22 | /.vscode
23 | /.zed
24 |
--------------------------------------------------------------------------------
/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | handleCommand(new ArgvInput);
14 |
15 | exit($status);
16 |
--------------------------------------------------------------------------------
/resources/views/components/radio-group/index.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'name' => '',
3 | 'defaultValue' => '',
4 | ])
5 |
6 | twMerge('grid gap-2') }}
13 | >
14 | {{ $slot }}
15 |
16 |
--------------------------------------------------------------------------------
/database/seeders/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | call([
15 | UserSeeder::class,
16 | TodoSeeder::class,
17 | ]);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/database/settings/2024_12_29_210810_add_self_hosted.php:
--------------------------------------------------------------------------------
1 | migrator->add('instance.is_self_hosted', true);
10 | }
11 |
12 | public function down(): void
13 | {
14 | $this->migrator->delete('instance.is_self_hosted');
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/public/vendor/log-viewer/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/app.js": "/app.js?id=6c51578cafcbf2f5e90fbf568a61ab8f",
3 | "/app.css": "/app.css?id=5593a0331dd40729ff41e32a6035d872",
4 | "/img/log-viewer-128.png": "/img/log-viewer-128.png?id=d576c6d2e16074d3f064e60fe4f35166",
5 | "/img/log-viewer-32.png": "/img/log-viewer-32.png?id=f8ec67d10f996aa8baf00df3b61eea6d",
6 | "/img/log-viewer-64.png": "/img/log-viewer-64.png?id=8902d596fc883ca9eb8105bb683568c6"
7 | }
8 |
--------------------------------------------------------------------------------
/resources/views/components/select/index.blade.php:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/resources/views/components/separator/index.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'orientation' => 'horizontal',
3 | 'decorative' => true,
4 | ])
5 | class([
8 | 'shrink-0 bg-border',
9 | 'h-px w-full' => $orientation == 'horizontal',
10 | 'h-full w-px' => $orientation == 'vertical',
11 | ])->merge([]) }}>
12 |
13 |
--------------------------------------------------------------------------------
/resources/views/components/popover/index.blade.php:
--------------------------------------------------------------------------------
1 |
11 | {{ $slot }}
12 |
13 |
--------------------------------------------------------------------------------
/app/Actions/Fortify/PasswordValidationRules.php:
--------------------------------------------------------------------------------
1 | |string>
13 | */
14 | protected function passwordRules(): array
15 | {
16 | return ['required', 'string', Password::default(), 'confirmed'];
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/resources/views/components/switch/index.blade.php:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | handleRequest(Request::capture());
18 |
--------------------------------------------------------------------------------
/resources/js/echo.js:
--------------------------------------------------------------------------------
1 | // import Echo from 'laravel-echo';
2 |
3 | // import Pusher from 'pusher-js';
4 | // window.Pusher = Pusher;
5 |
6 | // window.Echo = new Echo({
7 | // broadcaster: 'reverb',
8 | // key: import.meta.env.VITE_REVERB_APP_KEY,
9 | // wsHost: import.meta.env.VITE_REVERB_HOST,
10 | // wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
11 | // wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
12 | // forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
13 | // enabledTransports: ['ws', 'wss'],
14 | // });
15 |
--------------------------------------------------------------------------------
/config/tall-toasts.php:
--------------------------------------------------------------------------------
1 | 3000,
8 |
9 | /*
10 | * How long to wait before displaying the toasts after page loads, in ms
11 | */
12 | 'load_delay' => 400,
13 |
14 | /*
15 | * Session keys used.
16 | * No need to edit unless the keys are already being used and conflict.
17 | */
18 | 'session_keys' => [
19 | 'toasts' => 'toasts',
20 | 'toasts_next_page' => 'toasts-next',
21 | ],
22 | ];
23 |
--------------------------------------------------------------------------------
/app/View/Components/Aside.php:
--------------------------------------------------------------------------------
1 | info('Horizon is enabled on this server.');
17 | $this->call('horizon');
18 | exit(0);
19 | } else {
20 | exit(0);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/Console/Commands/StartScheduler.php:
--------------------------------------------------------------------------------
1 | info('Scheduler is enabled on this server.');
17 | $this->call('schedule:work');
18 | exit(0);
19 | } else {
20 | exit(0);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/content.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'align' => 'center',
3 | 'side' => 'bottom',
4 | 'sideOffset' => 4,
5 | ])
6 | @php
7 | $alignment = $side . ['center' => '', 'end' => '-end', 'start' => '-start'][$align];
8 | @endphp
9 | twMerge('z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md') }}>
13 | {{ $slot }}
14 |
15 |
--------------------------------------------------------------------------------
/app/Console/Commands/StartMigration.php:
--------------------------------------------------------------------------------
1 | info('Migration is enabled on this server.');
17 | $this->call('migrate', ['--force' => true]);
18 | exit(0);
19 | } else {
20 | exit(0);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/database/seeders/UserSeeder.php:
--------------------------------------------------------------------------------
1 | exists();
16 | if (! $userExists) {
17 | User::factory()->create([
18 | 'name' => 'Test User',
19 | 'email' => 'test@example.com',
20 | 'is_root_user' => true,
21 | ]);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/View/Components/Logo.php:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 | blade;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/resources/views/components/button/index.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'variant' => null,
3 | 'size' => null,
4 | 'type' => 'button',
5 | ])
6 |
7 | @inject('button', 'App\Services\ButtonCvaService')
8 |
9 | @php
10 | $wireTarget = $attributes->get('wire:target');
11 | @endphp
12 |
13 |
20 |
--------------------------------------------------------------------------------
/app/Models/Purchase.php:
--------------------------------------------------------------------------------
1 | belongsTo(User::class);
22 | }
23 |
24 | public function active(): bool
25 | {
26 | return $this->stripe_status === 'paid';
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/resources/views/components/hover-card/content.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'align' => 'center',
3 | 'side' => 'bottom',
4 | 'sideOffset' => 4,
5 | ])
6 | @php
7 | $alignment = $side . ['center' => '', 'end' => '-end', 'start' => '-start'][$align];
8 | @endphp
9 |
10 | twMerge('w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none') }}>
14 | {{ $slot }}
15 |
16 |
--------------------------------------------------------------------------------
/resources/views/components/tooltip/content.blade.php:
--------------------------------------------------------------------------------
1 | @aware([
2 | 'delayDuration' => 700,
3 | ])
4 |
5 | @props([
6 | 'align' => 'center',
7 | 'side' => 'top',
8 | 'sideOffset' => 4,
9 | ])
10 | @php
11 | $alignment = $side . ['center' => '', 'end' => '-end', 'start' => '-start'][$align];
12 | @endphp
13 |
14 | twMerge('z-50 overflow-hidden rounded-md px-3 py-1.5 text-xs bg-background text-primary-foreground') }}>
17 | {{ $slot }}
18 |
19 |
--------------------------------------------------------------------------------
/resources/views/components/popover/content.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'align' => 'center',
3 | 'side' => 'bottom',
4 | 'sideOffset' => 4,
5 | ])
6 | @php
7 | $alignment = $side . ['center' => '', 'end' => '-end', 'start' => '-start'][$align];
8 | @endphp
9 | twMerge('z-50 p-4 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md') }}>
13 | {{ $slot }}
14 |
15 |
--------------------------------------------------------------------------------
/app/Events/TodoUpdated.php:
--------------------------------------------------------------------------------
1 | user_id),
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/Http/Middleware/EnsureInstanceAdmin.php:
--------------------------------------------------------------------------------
1 | user()->is_root_user === false) {
19 | return redirect()->route('dashboard');
20 | }
21 |
22 | return $next($request);
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/components/avatar/index.blade.php:
--------------------------------------------------------------------------------
1 | twMerge('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full') }}
18 | >
19 | {{ $slot }}
20 |
21 |
--------------------------------------------------------------------------------
/resources/views/livewire/forms/hashtag-list.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @foreach ($todo->hashtags as $hashtag)
3 | @if ($clickable)
4 |
9 | @else
10 |
11 | #{{ $hashtag->name }}
12 |
13 | @endif
14 | @endforeach
15 |
16 |
--------------------------------------------------------------------------------
/database/migrations/2022_12_14_083707_create_settings_table.php:
--------------------------------------------------------------------------------
1 | id();
13 |
14 | $table->string('group');
15 | $table->string('name');
16 | $table->boolean('locked')->default(false);
17 | $table->json('payload');
18 |
19 | $table->timestamps();
20 |
21 | $table->unique(['group', 'name']);
22 | });
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/app/Http/Middleware/EnsurePaymentRoutesRegistered.php:
--------------------------------------------------------------------------------
1 | is_payment_enabled) {
20 | return $next($request);
21 | }
22 |
23 | return redirect()->route('dashboard');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/database/settings/2024_12_28_210810_create_instance_settings.php:
--------------------------------------------------------------------------------
1 | migrator->add('instance.is_registration_enabled', true);
10 | $this->migrator->add('instance.is_payment_enabled', false);
11 |
12 | $this->migrator->add('instance.stripe_key', null, true);
13 | $this->migrator->add('instance.stripe_secret', null, true);
14 | $this->migrator->add('instance.stripe_webhook_secret', null, true);
15 |
16 | $this->migrator->add('instance.subscriptions', []);
17 | $this->migrator->add('instance.one_time_payments', []);
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/resources/views/components/sheet/content.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'side' => 'right',
3 | ])
4 |
5 | @inject('sheet', 'App\Services\DialogCvaService')
6 |
7 |
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "type": "module",
4 | "scripts": {
5 | "build": "vite build",
6 | "dev": "vite"
7 | },
8 | "devDependencies": {
9 | "@vitejs/plugin-vue": "^5.2.1",
10 | "@tailwindcss/forms": "^0.5.10",
11 | "autoprefixer": "^10.4.20",
12 | "axios": "^1.7.4",
13 | "concurrently": "^9.0.1",
14 | "laravel-echo": "^1.16.1",
15 | "laravel-vite-plugin": "^1.0",
16 | "postcss": "^8.4.47",
17 | "pusher-js": "^8.4.0-rc2",
18 | "tailwindcss": "^3.4.13",
19 | "tailwindcss-animate": "^1.0.7",
20 | "vite": "^5.0"
21 | },
22 | "dependencies": {
23 | "@alpinejs/anchor": "^3.14.8",
24 | "@alpinejs/collapse": "^3.14.8"
25 | }
26 | }
--------------------------------------------------------------------------------
/database/migrations/2025_01_15_090304_create_hashtags_table.php:
--------------------------------------------------------------------------------
1 | id();
13 | $table->foreignId('user_id')->constrained()->cascadeOnDelete();
14 | $table->string('name');
15 | $table->timestamps();
16 |
17 | // Make name unique per user
18 | $table->unique(['user_id', 'name']);
19 | });
20 | }
21 |
22 | public function down()
23 | {
24 | Schema::dropIfExists('hashtags');
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/resources/views/components/form/input.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'type' => 'text',
3 | 'label' => 'Label',
4 | 'descriptionTrailing' => '',
5 | 'copy' => true,
6 | 'target' => null,
7 | ])
8 |
9 |
10 | @if ($type !== 'search')
11 |
12 | {{ $label }}
13 |
14 | @endif
15 |
16 |
17 | @if ($descriptionTrailing)
18 |
19 | {{ $descriptionTrailing }}
20 |
21 | @endif
22 |
23 |
25 |
26 |
--------------------------------------------------------------------------------
/database/migrations/2025_01_15_090306_create_hashtag_todo_table.php:
--------------------------------------------------------------------------------
1 | id();
13 | $table->foreignId('hashtag_id')->constrained()->onDelete('cascade');
14 | $table->foreignId('todo_id')->constrained()->onDelete('cascade');
15 | $table->timestamps();
16 |
17 | $table->unique(['hashtag_id', 'todo_id']);
18 | });
19 | }
20 |
21 | public function down()
22 | {
23 | Schema::dropIfExists('hashtag_todo');
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/item.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'disabled' => false,
3 | 'inset' => false,
4 | ])
5 |
6 | when($disabled, function ($attributes) {
8 | return $attributes->except(['x-on:click', '@click', 'wire:click']);
9 | })->twMerge([
10 | 'hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
11 | 'relative flex w-full cursor-pointer select-none items-center',
12 | 'rounded-sm px-2 py-1.5 text-sm outline-none transition-colors',
13 | 'opacity-50 cursor-not-allowed' => $disabled,
14 | 'pl-8' => $inset,
15 | ]) }}>
16 | {{ $slot }}
17 |
18 |
--------------------------------------------------------------------------------
/resources/views/components/tabs/trigger.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'value' => '',
3 | 'checked' => false,
4 | ])
5 |
6 |
7 |
13 |
14 | twMerge('inline-flex w-full items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all cursor-pointer peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 peer-checked:bg-background peer-checked:text-foreground peer-checked:shadow') }}
17 | >
18 | {{ $slot }}
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/Console/Commands/StartSeeder.php:
--------------------------------------------------------------------------------
1 | info('Seeder is enabled on this server.');
17 | if ($this->option('production')) {
18 | $this->call('db:seed', ['--class' => 'ProductionSeeder', '--force' => true]);
19 | } else {
20 | $this->call('db:seed', ['--force' => true]);
21 | }
22 | exit(0);
23 | } else {
24 | exit(0);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Http/Middleware/EnsureSubscription.php:
--------------------------------------------------------------------------------
1 | user()->paid())) {
24 | return redirect()->route('billing');
25 | }
26 |
27 | return $next($request);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/resources/views/components/dialog/save.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'variant' => 'outline',
3 | ])
4 |
5 | @php
6 | $wireAttributes = $attributes->whereStartsWith('wire:')->getAttributes();
7 | $hasWireAction = !empty($wireAttributes);
8 | $clickHandler = $hasWireAction
9 | ? "if (!clicked) { clicked = true; \$wire.\$call(\$el.getAttribute('wire:click')).then(() => { __dialogOpen = false; clicked = false; }); }"
10 | : '__dialogOpen = false';
11 | @endphp
12 |
13 | merge(['variant' => $variant]) }} x-data="{ clicked: false }"
14 | x-on:click="{{ $hasWireAction ? '.prevent' : '' }}" x-on:wire:loading.attr="disabled" x-on:click="{{ $clickHandler }}">
15 | @if ($slot->isEmpty())
16 | {{ __('Close') }}
17 | @else
18 | {{ $slot }}
19 | @endif
20 |
21 |
--------------------------------------------------------------------------------
/app/Actions/Fortify/ResetUserPassword.php:
--------------------------------------------------------------------------------
1 | $input
18 | */
19 | public function reset(User $user, array $input): void
20 | {
21 | Validator::make($input, [
22 | 'password' => $this->passwordRules(),
23 | ])->validate();
24 |
25 | $user->forceFill([
26 | 'password' => Hash::make($input['password']),
27 | ])->save();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/resources/views/components/dialog/content.blade.php:
--------------------------------------------------------------------------------
1 | @inject('dialog', 'App\Services\DialogCvaService')
2 |
3 |
14 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/sub/trigger.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'disabled' => false,
3 | 'inset' => false,
4 | ])
5 | except(['x-on:click', '@click', 'wire:click'])->twMerge([
8 | 'hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
9 | 'relative flex w-full cursor-default select-none items-center',
10 | 'rounded-sm px-2 py-1.5 text-sm outline-none transition-colors',
11 | 'opacity-50 cursor-not-allowed' => $disabled,
12 | 'pl-8' => $inset,
13 | ]) }}>
14 |
15 | {{ $slot }}
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/resources/views/components/aside.blade.php:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/docker/development/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM serversideup/php:8.3-fpm-nginx-bookworm
2 | ENV BASE_DIR="docker/development"
3 | ENV APP_ENV=local
4 |
5 | ARG USER_ID
6 | ARG GROUP_ID
7 |
8 | USER root
9 | RUN apt-get update && \
10 | apt-get install -y vim && \
11 | apt-get -y autoremove && \
12 | apt-get clean && \
13 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
14 |
15 | RUN install-php-extensions bcmath
16 |
17 | RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID
18 | RUN docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx
19 |
20 | USER www-data
21 | WORKDIR /var/www/html
22 |
23 | COPY ${BASE_DIR}/nginx.conf /etc/nginx/server-opts.d/reverb.conf
24 | COPY --chmod=755 ${BASE_DIR}/entrypoint.d /etc/entrypoint.d
25 |
26 | COPY --chmod=755 --chown=www-data:www-data ${BASE_DIR}/etc/s6-overlay/ /etc/s6-overlay/
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/Models/Hashtag.php:
--------------------------------------------------------------------------------
1 | belongsTo(User::class);
20 | }
21 |
22 | public function todos(): BelongsToMany
23 | {
24 | return $this->belongsToMany(Todo::class);
25 | }
26 |
27 | public static function search(string $query): array
28 | {
29 | return static::where('user_id', Auth::id())
30 | ->where('name', 'like', "%{$query}%")
31 | ->pluck('name')
32 | ->toArray();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/docker-compose.dev.yml:
--------------------------------------------------------------------------------
1 | services:
2 | php:
3 | build:
4 | context: .
5 | dockerfile: ./docker/development/Dockerfile
6 | args:
7 | USER_ID: ${UID:-1000}
8 | GROUP_ID: ${GID:-1000}
9 | ports:
10 | - "8000:8080"
11 | - "9999:9999"
12 | env_file:
13 | - .env.development
14 | volumes:
15 | - .:/var/www/html/
16 | depends_on:
17 | - redis
18 | vite:
19 | image: node:22-alpine
20 | pull_policy: always
21 | working_dir: /var/www/html
22 | environment:
23 | VITE_HOST: "${VITE_HOST:-localhost}"
24 | VITE_PORT: "${VITE_PORT:-5173}"
25 | ports:
26 | - "${VITE_PORT:-5173}:${VITE_PORT:-5173}"
27 | volumes:
28 | - .:/var/www/html/:cached
29 | command: sh -c "npm install && npm run dev"
30 | redis:
31 | image: redis:7-alpine
32 | pull_policy: always
33 | env_file:
34 | - .env.development
35 |
--------------------------------------------------------------------------------
/config/constants.php:
--------------------------------------------------------------------------------
1 | [
5 | 'version' => '1.0.0',
6 | 'self_hosted' => env('SELF_HOSTED', true),
7 | ],
8 |
9 | 'pusher' => [
10 | 'host' => env('PUSHER_HOST'),
11 | 'port' => env('PUSHER_PORT'),
12 | 'app_key' => env('PUSHER_APP_KEY'),
13 | ],
14 |
15 | 'migration' => [
16 | 'is_migration_enabled' => env('MIGRATION_ENABLED', true),
17 | ],
18 |
19 | 'seeder' => [
20 | 'is_seeder_enabled' => env('SEEDER_ENABLED', true),
21 | ],
22 |
23 | 'horizon' => [
24 | 'is_horizon_enabled' => env('HORIZON_ENABLED', true),
25 | 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true),
26 | ],
27 |
28 | 'stripe' => [
29 | 'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
30 | 'api_key' => env('STRIPE_API_KEY'),
31 | 'webhook_url' => env('STRIPE_WEBHOOK_URL'),
32 | ],
33 | ];
34 |
--------------------------------------------------------------------------------
/resources/views/livewire/auth/login.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Jata
5 |
6 |
7 | Just Another Todo App
8 |
9 |
10 |
11 |
12 | Login
13 | @if ($isRegistrationEnabled)
14 |
17 | @endif
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/database/migrations/2024_03_14_000000_create_purchases_table.php:
--------------------------------------------------------------------------------
1 | id();
13 | $table->foreignId('user_id');
14 | $table->string('stripe_id')->unique();
15 | $table->string('stripe_payment_intent');
16 | $table->string('stripe_price');
17 | $table->string('stripe_status');
18 | $table->integer('quantity')->default(1);
19 | $table->timestamps();
20 |
21 | $table->index(['user_id', 'stripe_status']);
22 | });
23 | }
24 |
25 | public function down(): void
26 | {
27 | Schema::dropIfExists('purchases');
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig, loadEnv } from 'vite';
2 | import laravel from 'laravel-vite-plugin';
3 | import vue from '@vitejs/plugin-vue';
4 |
5 | export default defineConfig(({ mode }) => {
6 | const env = loadEnv(mode, process.cwd(), '')
7 | return {
8 | server: process.env.NODE_ENV === 'development' ? {
9 | host: '0.0.0.0',
10 | hmr: {
11 | host: env.VITE_HOST || '0.0.0.0',
12 | },
13 | } : {},
14 |
15 | plugins: [
16 | laravel({
17 | input: ['resources/css/app.css', 'resources/js/app.js'],
18 | refresh: true,
19 | }),
20 | vue({
21 | template: {
22 | transformAssetUrls: {
23 | base: null,
24 | includeAbsolute: false,
25 | },
26 | },
27 | }),
28 | ],
29 | };
30 | });
31 |
--------------------------------------------------------------------------------
/resources/js/app.js:
--------------------------------------------------------------------------------
1 | // import './bootstrap';
2 |
3 | import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
4 | import ToastComponent from '../../vendor/usernotnull/tall-toasts/resources/js/tall-toasts'
5 | import collapse from "@alpinejs/collapse";
6 | import anchor from "@alpinejs/anchor";
7 |
8 | document.addEventListener(
9 | "alpine:init",
10 | () => {
11 | const modules = import.meta.glob("./plugins/**/*.js", { eager: true });
12 | for (const path in modules) {
13 | window.Alpine.plugin(modules[path].default);
14 | }
15 | window.Alpine.plugin(collapse);
16 | window.Alpine.plugin(anchor);
17 | window.Alpine.plugin(ToastComponent);
18 | },
19 | { once: true },
20 | );
21 | Alpine.directive('clipboard', (el) => {
22 | let text = el.textContent
23 |
24 | el.addEventListener('click', () => {
25 | navigator.clipboard.writeText(text)
26 | })
27 | })
28 |
29 | Livewire.start()
30 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/checkboxitem.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'disabled' => false,
3 | ])
4 | when($disabled, function ($attributes) {
7 | return $attributes->except(['x-model', 'wire:model']);
8 | })->twMerge([
9 | 'hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
10 | 'relative flex w-full cursor-pointer select-none items-center',
11 | 'rounded-sm py-1.5 text-sm outline-none transition-colors',
12 | $disabled ? 'opacity-50 cursor-not-allowed' : '',
13 | 'pl-8',
14 | ]) }}>
15 |
16 |
17 |
18 |
19 | {{ $slot }}
20 |
21 |
--------------------------------------------------------------------------------
/resources/views/components/textarea/index.blade.php:
--------------------------------------------------------------------------------
1 | @props(['target' => null])
2 |
3 |
5 | @if ($target)
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | @endif
15 |
16 |
--------------------------------------------------------------------------------
/app/Services/AlertCvaService.php:
--------------------------------------------------------------------------------
1 | svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
14 | ],
15 | [
16 | 'variants' => [
17 | 'variant' => [
18 | 'default' => 'bg-background text-foreground',
19 | 'destructive' => 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
20 | ],
21 | ],
22 | 'defaultVariants' => [
23 | 'variant' => 'default',
24 | ],
25 | ],
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/database/migrations/2025_01_14_090304_create_todos_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('title');
17 | $table->enum('status', ['backlog', 'in_progress', 'completed'])->default('backlog');
18 | $table->text('description')->nullable();
19 | $table->dateTime('worked_at')->default(now())->nullable();
20 | $table->foreignId('user_id')->constrained('users');
21 | $table->timestamps();
22 | });
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | */
28 | public function down(): void
29 | {
30 | Schema::dropIfExists('todos');
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/database/migrations/0001_01_01_000001_create_cache_table.php:
--------------------------------------------------------------------------------
1 | string('key')->primary();
16 | $table->mediumText('value');
17 | $table->integer('expiration');
18 | });
19 |
20 | Schema::create('cache_locks', function (Blueprint $table) {
21 | $table->string('key')->primary();
22 | $table->string('owner');
23 | $table->integer('expiration');
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | */
30 | public function down(): void
31 | {
32 | Schema::dropIfExists('cache');
33 | Schema::dropIfExists('cache_locks');
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/app/Providers/HorizonServiceProvider.php:
--------------------------------------------------------------------------------
1 | id === User::first()->id;
33 | });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/database/migrations/2025_01_08_080321_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->index(['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 |
--------------------------------------------------------------------------------
/resources/views/components/accordion/trigger.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
--------------------------------------------------------------------------------
/app/View/Components/Navigation.php:
--------------------------------------------------------------------------------
1 | isRootUser = auth()->user()->is_root_user;
23 | $this->isPaymentEnabled = $instanceSettings->is_payment_enabled;
24 | $this->isRegistrationEnabled = $instanceSettings->is_registration_enabled;
25 | $this->isCloudEnabled = $instanceSettings->isCloud();
26 | }
27 |
28 | /**
29 | * Get the view / contents that represent the component.
30 | */
31 | public function render(): View|Closure|string
32 | {
33 | return view('components.navigation');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/Actions/Fortify/UpdateUserPassword.php:
--------------------------------------------------------------------------------
1 | $input
18 | */
19 | public function update(User $user, array $input): void
20 | {
21 | Validator::make($input, [
22 | 'current_password' => ['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 |
--------------------------------------------------------------------------------
/docker/development/nginx.conf:
--------------------------------------------------------------------------------
1 | # Custom nginx configuration
2 | # Laravel Reverb
3 | location /app/ {
4 | proxy_pass http://localhost:9999;
5 | proxy_set_header Host $host;
6 | proxy_set_header X-Real-IP $remote_addr;
7 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
8 | proxy_set_header X-Forwarded-Proto $scheme;
9 |
10 | # WebSocket specific settings
11 | proxy_http_version 1.1;
12 | proxy_set_header Upgrade $http_upgrade;
13 | proxy_set_header Connection "upgrade";
14 | proxy_read_timeout 86400;
15 | }
16 | location /apps/ {
17 | proxy_pass http://localhost:9999;
18 | proxy_set_header Host $host;
19 | proxy_set_header X-Real-IP $remote_addr;
20 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
21 | proxy_set_header X-Forwarded-Proto $scheme;
22 |
23 | # WebSocket specific settings
24 | proxy_http_version 1.1;
25 | proxy_set_header Upgrade $http_upgrade;
26 | proxy_set_header Connection "upgrade";
27 | proxy_read_timeout 86400;
28 | }
29 |
30 | # Disable access logs
31 | access_log on;
32 |
--------------------------------------------------------------------------------
/docker/production/nginx.conf:
--------------------------------------------------------------------------------
1 | # Custom nginx configuration
2 | # Laravel Reverb
3 | location /app/ {
4 | proxy_pass http://localhost:9999;
5 | proxy_set_header Host $host;
6 | proxy_set_header X-Real-IP $remote_addr;
7 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
8 | proxy_set_header X-Forwarded-Proto $scheme;
9 |
10 | # WebSocket specific settings
11 | proxy_http_version 1.1;
12 | proxy_set_header Upgrade $http_upgrade;
13 | proxy_set_header Connection "upgrade";
14 | proxy_read_timeout 86400;
15 | }
16 | location /apps/ {
17 | proxy_pass http://localhost:9999;
18 | proxy_set_header Host $host;
19 | proxy_set_header X-Real-IP $remote_addr;
20 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
21 | proxy_set_header X-Forwarded-Proto $scheme;
22 |
23 | # WebSocket specific settings
24 | proxy_http_version 1.1;
25 | proxy_set_header Upgrade $http_upgrade;
26 | proxy_set_header Connection "upgrade";
27 | proxy_read_timeout 86400;
28 | }
29 |
30 | # Disable access logs
31 | access_log on;
32 |
--------------------------------------------------------------------------------
/public/vendor/log-viewer/app.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * The buffer module from node.js, for the browser.
3 | *
4 | * @author Feross Aboukhadijeh
5 | * @license MIT
6 | */
7 |
8 | /*!
9 | * pinia v2.2.2
10 | * (c) 2024 Eduardo San Martin Morote
11 | * @license MIT
12 | */
13 |
14 | /*! #__NO_SIDE_EFFECTS__ */
15 |
16 | /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */
17 |
18 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
19 |
20 | /**
21 | * @license
22 | * Lodash
23 | * Copyright OpenJS Foundation and other contributors
24 | * Released under MIT license
25 | * Based on Underscore.js 1.8.3
26 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
27 | */
28 |
29 | /**
30 | * @vue/shared v3.4.38
31 | * (c) 2018-present Yuxi (Evan) You and Vue contributors
32 | * @license MIT
33 | **/
34 |
--------------------------------------------------------------------------------
/app/Settings/InstanceSettings.php:
--------------------------------------------------------------------------------
1 | is_self_hosted;
33 | }
34 |
35 | public static function isCloud(): bool
36 | {
37 | return ! app(self::class)->is_self_hosted;
38 | }
39 |
40 | public static function setCloud(bool $value): void
41 | {
42 | app(self::class)->is_self_hosted = ! $value;
43 | app(self::class)->save();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown-menu/radioitem.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'disabled' => false,
3 | 'value' => '',
4 | ])
5 | when($disabled, function ($attributes) {
13 | return $attributes->except(['x-model', 'wire:model']);
14 | })->twMerge([
15 | 'hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
16 | 'relative flex w-full cursor-default select-none items-center',
17 | 'rounded-sm py-1.5 text-sm outline-none transition-colors',
18 | 'opacity-50 cursor-not-allowed' => $disabled,
19 | 'pl-8',
20 | ]) }}
21 | >
22 |
26 |
27 |
28 | {{ $slot }}
29 |
30 |
--------------------------------------------------------------------------------
/app/Http/Middleware/HandleInertiaRequests.php:
--------------------------------------------------------------------------------
1 |
35 | */
36 | public function share(Request $request): array
37 | {
38 | return array_merge(parent::share($request), [
39 | //
40 | ]);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/Livewire/Billing.php:
--------------------------------------------------------------------------------
1 | route('dashboard');
21 | }
22 |
23 | $this->subscriptions = $instanceSettings->subscriptions;
24 | }
25 |
26 | public function subscribeToSubscription($name)
27 | {
28 | $subscription = collect($this->subscriptions)->firstWhere('name', $name);
29 | session()->put('price_id', $subscription['price_id']);
30 |
31 | if ($subscription['is_one_time_payment']) {
32 | return redirect()->route('one-time-payment-checkout');
33 | }
34 |
35 | return redirect()->route('subscription-checkout');
36 | }
37 |
38 | public function render()
39 | {
40 | return view('livewire.billing');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/config/services.php:
--------------------------------------------------------------------------------
1 | [
18 | 'token' => env('POSTMARK_TOKEN'),
19 | ],
20 |
21 | 'ses' => [
22 | 'key' => env('AWS_ACCESS_KEY_ID'),
23 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
24 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
25 | ],
26 |
27 | 'resend' => [
28 | 'key' => env('RESEND_KEY'),
29 | ],
30 |
31 | 'slack' => [
32 | 'notifications' => [
33 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
34 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
35 | ],
36 | ],
37 |
38 | ];
39 |
--------------------------------------------------------------------------------
/database/migrations/2025_01_08_080321_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->dropIndex([
29 | 'stripe_id',
30 | ]);
31 |
32 | $table->dropColumn([
33 | 'stripe_id',
34 | 'pm_type',
35 | 'pm_last_four',
36 | 'trial_ends_at',
37 | ]);
38 | });
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/database/migrations/2025_01_08_080322_create_subscriptions_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->foreignId('user_id');
17 | $table->string('type');
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 |
--------------------------------------------------------------------------------
/app/Actions/Fortify/CreateNewUser.php:
--------------------------------------------------------------------------------
1 | $input
19 | */
20 | public function create(array $input): User
21 | {
22 | Validator::make($input, [
23 | 'name' => ['required', 'string', 'max:255'],
24 | 'email' => [
25 | 'required',
26 | 'string',
27 | 'email',
28 | 'max:255',
29 | Rule::unique(User::class),
30 | ],
31 | 'password' => $this->passwordRules(),
32 | ])->validate();
33 |
34 | return User::create([
35 | 'name' => $input['name'],
36 | 'email' => $input['email'],
37 | 'password' => Hash::make($input['password']),
38 | ]);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/resources/views/livewire/auth/register.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Register
5 |
6 |
7 |
8 |
9 |
10 |
11 | Register
12 |
13 | @if ($isRootUser)
14 | You are about to create the first user.
This user will have full access to
15 | the
16 | system
17 | (root user).
18 | @else
19 |
23 | @endif
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/Services/BadgeCvaService.php:
--------------------------------------------------------------------------------
1 | [
17 | 'variant' => [
18 | 'default' => 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
19 | 'secondary' => 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
20 | 'destructive' => 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
21 | 'outline' => 'text-foreground',
22 | ],
23 | ],
24 | 'defaultVariants' => [
25 | 'variant' => 'default',
26 | ],
27 | ],
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | $job->donation->user_id]);
27 |
28 | return Limit::perSecond(1, 10)->by($job->donation->user_id);
29 | });
30 | LogViewer::auth(function ($request) {
31 | if (config('app.env') === 'local') {
32 | return true;
33 | }
34 |
35 | return $request->user()
36 | && $request->user()->id === User::first()->id;
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/scripts/production-build-locally.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Default values
4 | NO_CACHE=false
5 | PORT=8888
6 | CONTAINER_NAME="php"
7 | IMAGE_NAME="php"
8 | ENV_FILE=".env.production"
9 |
10 | # Parse arguments
11 | while [[ "$#" -gt 0 ]]; do
12 | case $1 in
13 | --no-cache) NO_CACHE=true ;;
14 | --port) PORT="$2"; shift ;;
15 | *) echo "Unknown parameter: $1"; exit 1 ;;
16 | esac
17 | shift
18 | done
19 |
20 | # Build the image
21 | echo "Building Docker image..."
22 | if [ "$NO_CACHE" = true ]; then
23 | docker build --no-cache -t $IMAGE_NAME -f docker/production/Dockerfile .
24 | else
25 | docker build -t $IMAGE_NAME -f docker/production/Dockerfile .
26 | fi
27 |
28 | # Check if build was successful
29 | if [ $? -eq 0 ]; then
30 | echo "Build successful. Starting container..."
31 |
32 | # Check if container already exists
33 | if docker ps -a | grep -q $CONTAINER_NAME; then
34 | echo "Removing existing container..."
35 | docker rm -f $CONTAINER_NAME
36 | fi
37 |
38 | # Run the container
39 | docker run --rm -ti \
40 | -p $PORT:8080 \
41 | --env-file $ENV_FILE \
42 | --name $CONTAINER_NAME \
43 | $IMAGE_NAME
44 | else
45 | echo "Build failed!"
46 | exit 1
47 | fi
48 |
--------------------------------------------------------------------------------
/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class UserFactory extends Factory
13 | {
14 | /**
15 | * The current password being used by the factory.
16 | */
17 | protected static ?string $password;
18 |
19 | /**
20 | * Define the model's default state.
21 | *
22 | * @return array
23 | */
24 | public function definition(): array
25 | {
26 | return [
27 | 'name' => fake()->name(),
28 | 'email' => fake()->unique()->safeEmail(),
29 | 'email_verified_at' => now(),
30 | 'password' => static::$password ??= Hash::make('password'),
31 | 'remember_token' => Str::random(10),
32 | ];
33 | }
34 |
35 | /**
36 | * Indicate that the model's email address should be unverified.
37 | */
38 | public function unverified(): static
39 | {
40 | return $this->state(fn (array $attributes) => [
41 | 'email_verified_at' => null,
42 | ]);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/config/laravelpwa.php:
--------------------------------------------------------------------------------
1 | 'Jata',
5 | 'manifest' => [
6 | 'name' => 'Jata',
7 | 'short_name' => 'Jata',
8 | 'start_url' => '/',
9 | 'background_color' => '#000000',
10 | 'theme_color' => '#000000',
11 | 'display' => 'fullscreen',
12 | 'orientation' => 'any',
13 | 'status_bar' => 'black',
14 | 'icons' => [
15 | '72x72' => [
16 | 'path' => '/images/icons/android-launchericon-72-72.png',
17 | 'purpose' => 'any',
18 | ],
19 | '96x96' => [
20 | 'path' => '/images/icons/android-launchericon-96-96.png',
21 | 'purpose' => 'any',
22 | ],
23 | '128x128' => [
24 | 'path' => '/images/icons/android-launchericon-128-128.png',
25 | 'purpose' => 'any',
26 | ],
27 | '144x144' => [
28 | 'path' => '/images/icons/android-launchericon-144-144.png',
29 | 'purpose' => 'any',
30 | ],
31 | '512x512' => [
32 | 'path' => '/images/icons/android-launchericon-512-512.png',
33 | 'purpose' => 'any',
34 | ],
35 |
36 | ],
37 | 'custom' => [],
38 | ],
39 | ];
40 |
--------------------------------------------------------------------------------
/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 |
33 |
34 |
--------------------------------------------------------------------------------
/app/Livewire/Forms/HashtagList.php:
--------------------------------------------------------------------------------
1 | '$refresh',
18 | ];
19 | }
20 |
21 | public function removeHashtag($hashtagId)
22 | {
23 | if (! $this->clickable) {
24 | return;
25 | }
26 |
27 | // Get the hashtag before detaching
28 | $hashtag = $this->todo->hashtags()->find($hashtagId);
29 | if (!$hashtag) {
30 | return;
31 | }
32 |
33 | // Detach the hashtag from the todo
34 | $this->todo->hashtags()->detach($hashtagId);
35 |
36 | // If this was the last todo using this hashtag, delete it
37 | if ($hashtag->todos()->count() === 0) {
38 | $hashtag->delete();
39 | }
40 |
41 | $this->todo->refresh();
42 |
43 | // Dispatch event to refresh parent components
44 | $this->dispatch('hashtags-updated');
45 | }
46 |
47 | public function render()
48 | {
49 | return view('livewire.forms.hashtag-list');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | withRouting(
12 | web: __DIR__.'/../routes/web.php',
13 | api: __DIR__.'/../routes/api.php',
14 | commands: __DIR__.'/../routes/console.php',
15 | channels: __DIR__.'/../routes/channels.php',
16 | health: '/up',
17 | )
18 | ->withMiddleware(function (Middleware $middleware) {
19 | $middleware->trustProxies(
20 | at: '*',
21 | headers: Request::HEADER_X_FORWARDED_FOR |
22 | Request::HEADER_X_FORWARDED_HOST |
23 | Request::HEADER_X_FORWARDED_PORT |
24 | Request::HEADER_X_FORWARDED_PROTO |
25 | Request::HEADER_X_FORWARDED_AWS_ELB
26 | );
27 | $middleware->web(append: [
28 | HandleInertiaRequests::class,
29 | ]);
30 | })
31 | ->withExceptions(function (Exceptions $exceptions) {})
32 | ->withSchedule(function (Schedule $schedule) {})
33 | ->create();
34 |
--------------------------------------------------------------------------------
/database/migrations/2024_11_10_115838_add_two_factor_columns_to_users_table.php:
--------------------------------------------------------------------------------
1 | text('two_factor_secret')
17 | ->after('password')
18 | ->nullable();
19 |
20 | $table->text('two_factor_recovery_codes')
21 | ->after('two_factor_secret')
22 | ->nullable();
23 |
24 | if (Fortify::confirmsTwoFactorAuthentication()) {
25 | $table->timestamp('two_factor_confirmed_at')
26 | ->after('two_factor_recovery_codes')
27 | ->nullable();
28 | }
29 | });
30 | }
31 |
32 | /**
33 | * Reverse the migrations.
34 | */
35 | public function down(): void
36 | {
37 | Schema::table('users', function (Blueprint $table) {
38 | $table->dropColumn(array_merge([
39 | 'two_factor_secret',
40 | 'two_factor_recovery_codes',
41 | ], Fortify::confirmsTwoFactorAuthentication() ? [
42 | 'two_factor_confirmed_at',
43 | ] : []));
44 | });
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/docker/production/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM serversideup/php:8.3-fpm-nginx-bookworm AS base
2 | USER root
3 | WORKDIR /var/www/html
4 | ENV APP_ENV=production
5 | COPY composer.json composer.lock ./
6 | RUN install-php-extensions bcmath
7 | RUN composer install --optimize-autoloader --no-interaction --no-plugins --no-scripts --prefer-dist --no-dev
8 |
9 | FROM node:22 AS assets
10 | WORKDIR /app
11 | COPY package.json package-lock.json ./
12 | RUN npm ci
13 | COPY . .
14 | COPY --from=base /var/www/html/vendor ./vendor
15 | ARG NODE_ENV=production
16 | RUN npm run build
17 |
18 | FROM serversideup/php:8.3-fpm-nginx-bookworm AS prod
19 | WORKDIR /var/www/html
20 | ENV BASE_DIR="docker/production"
21 | ENV APP_ENV=production
22 |
23 | USER root
24 | RUN apt-get update && \
25 | apt-get install -y vim && \
26 | apt-get -y autoremove && \
27 | apt-get clean && \
28 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
29 |
30 | RUN install-php-extensions bcmath
31 |
32 | USER www-data
33 |
34 | COPY ${BASE_DIR}/nginx.conf /etc/nginx/server-opts.d/reverb.conf
35 |
36 | COPY --from=base --chown=www-data:www-data /var/www/html/vendor ./vendor
37 |
38 | COPY --chown=www-data:www-data . .
39 | COPY --chmod=755 --chown=www-data:www-data ${BASE_DIR}/etc/s6-overlay/ /etc/s6-overlay/
40 | COPY --from=assets --chown=www-data:www-data /app/public/build ./public/build
41 |
42 | RUN composer dump-autoload --optimize --no-scripts
43 |
44 | RUN mkdir -p /var/www/html/database/db
45 | RUN chmod 755 /var/www/html/database/db
46 |
--------------------------------------------------------------------------------
/config/tailwind-merge.php:
--------------------------------------------------------------------------------
1 | env('TAILWIND_MERGE_PREFIX', null),
15 |
16 | /*
17 | |--------------------------------------------------------------------------
18 | | Class groups
19 | |--------------------------------------------------------------------------
20 | |
21 | | If TailwindMerge is not able to merge your changes properly you can
22 | | modify the merge process by modifying existing class groups or adding
23 | | new class groups.
24 | |
25 | | For example, if you want to add a custom font size of 'very-large':
26 | | 'classGroups' => [
27 | | 'font-size' => [
28 | | ['text' => ['very-large']]
29 | | ],
30 | | ],
31 | */
32 |
33 | 'classGroups' => [],
34 |
35 | /*
36 | |--------------------------------------------------------------------------
37 | | Blade directive
38 | |--------------------------------------------------------------------------
39 | |
40 | | Here you may specify the name of the blade directive that will be used.
41 | | Set it to null to entirely disable the blade directive.
42 | */
43 |
44 | 'blade_directive' => 'twMerge',
45 | ];
46 |
--------------------------------------------------------------------------------
/resources/views/livewire/billing.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @php
3 | $access = auth()->user()->paid();
4 | @endphp
5 |
6 | @if ($access)
7 |
You have an active {{ $access['type'] }}: {{ $access['name'] }}
9 | @if ($access['type'] === 'subscription' && $access['name'] != 'iddqd')
10 |
Manage
11 | Subscription
12 | @endif
13 | @else
14 |
You do not have an active subscription. Please subscribe to
15 | continue.
16 |
17 | @forelse ($subscriptions as $subscription)
18 | @if ($subscription['is_one_time_payment'])
19 | Pay Once
21 | {{ data_get($subscription, 'name') }}
22 | @else
23 | Subscribe to
25 | {{ data_get($subscription, 'name') }}
26 | @endif
27 | @empty
28 | No subscriptions found. Contact support.
29 | @endforelse
30 |
31 | @endif
32 |
33 |
--------------------------------------------------------------------------------
/resources/js/plugins/tooltip.js:
--------------------------------------------------------------------------------
1 | export default function (Alpine) {
2 | Alpine.directive("tooltip", (el, directive) => {
3 | if (!directive.value) handleRoot(el, Alpine);
4 | else if (directive.value === "trigger") handleTrigger(el, Alpine);
5 | else if (directive.value === "content") handleContent(el, Alpine);
6 | }).before("bind");
7 | }
8 |
9 | function handleRoot(el, Alpine) {
10 | Alpine.bind(el, {
11 | "x-id"() {
12 | return ["alpine-tooltip", "alpine-tooltip-trigger"];
13 | },
14 | ":id"() {
15 | return this.$id("alpine-tooltip");
16 | },
17 | "x-data"() {
18 | return {
19 | __open: false,
20 | __close() {
21 | this.__open = false;
22 | },
23 | };
24 | },
25 | });
26 | }
27 |
28 | function handleTrigger(el, Alpine) {
29 | Alpine.bind(el, {
30 | "x-ref": "__tooltip-trigger",
31 | ":id"() {
32 | return this.$id("alpine-tooltip-trigger");
33 | },
34 | "@focusin"() {
35 | this.$data.__open = !this.$data.__open;
36 | },
37 | "@focusout"() {
38 | this.$data.__close();
39 | },
40 | "@mouseenter"() {
41 | this.$data.__open = !this.$data.__open;
42 | },
43 | "@mouseleave"() {
44 | this.$data.__close();
45 | },
46 | });
47 | }
48 |
49 | function handleContent(el, Alpine) {
50 | Alpine.bind(el, {
51 | "x-show"() {
52 | return this.$data.__open;
53 | },
54 | "x-cloak": true
55 | });
56 | }
57 |
--------------------------------------------------------------------------------
/public/serviceworker.js:
--------------------------------------------------------------------------------
1 | var staticCacheName = "pwa-v" + new Date().getTime();
2 | var filesToCache = [
3 | '/offline',
4 | '/css/app.css',
5 | '/js/app.js',
6 | '/images/icons/icon-72x72.png',
7 | '/images/icons/icon-96x96.png',
8 | '/images/icons/icon-128x128.png',
9 | '/images/icons/icon-144x144.png',
10 | '/images/icons/icon-152x152.png',
11 | '/images/icons/icon-192x192.png',
12 | '/images/icons/icon-384x384.png',
13 | '/images/icons/icon-512x512.png',
14 | ];
15 |
16 | // Cache on install
17 | self.addEventListener("install", event => {
18 | this.skipWaiting();
19 | event.waitUntil(
20 | caches.open(staticCacheName)
21 | .then(cache => {
22 | return cache.addAll(filesToCache);
23 | })
24 | )
25 | });
26 |
27 | // Clear cache on activate
28 | self.addEventListener('activate', event => {
29 | event.waitUntil(
30 | caches.keys().then(cacheNames => {
31 | return Promise.all(
32 | cacheNames
33 | .filter(cacheName => (cacheName.startsWith("pwa-")))
34 | .filter(cacheName => (cacheName !== staticCacheName))
35 | .map(cacheName => caches.delete(cacheName))
36 | );
37 | })
38 | );
39 | });
40 |
41 | // Serve from Cache
42 | self.addEventListener("fetch", event => {
43 | event.respondWith(
44 | caches.match(event.request)
45 | .then(response => {
46 | return response || fetch(event.request);
47 | })
48 | .catch(() => {
49 | return caches.match('offline');
50 | })
51 | )
52 | });
--------------------------------------------------------------------------------
/resources/views/vendor/tall-toasts/includes/content.blade.php:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
17 |
18 | {{--
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--}}
35 |
36 |
37 |
--------------------------------------------------------------------------------
/resources/js/plugins/hover-card.js:
--------------------------------------------------------------------------------
1 | export default function (Alpine) {
2 | Alpine.directive("hover-card", (el, directive) => {
3 | if (!directive.value) handleRoot(el, Alpine);
4 | else if (directive.value === "trigger") handleTrigger(el, Alpine);
5 | else if (directive.value === "content") handleContent(el, Alpine);
6 | }).before("bind");
7 | }
8 |
9 | function handleRoot(el, Alpine) {
10 | Alpine.bind(el, {
11 | "x-id"() {
12 | return ["alpine-hover-card", "alpine-hover-card-trigger"];
13 | },
14 | ":id"() {
15 | return this.$id("alpine-hover-card");
16 | },
17 | "x-data"() {
18 | return {
19 | __open: false,
20 | __openDelay: 700,
21 | __closeDelay: 300,
22 | __close() {
23 | this.__open = false;
24 | },
25 | };
26 | },
27 | });
28 | }
29 |
30 | function handleTrigger(el, Alpine) {
31 | Alpine.bind(el, {
32 | "x-ref": "__hover-card-trigger",
33 | ":id"() {
34 | return this.$id("alpine-hover-card-trigger");
35 | },
36 | "@mouseenter"() {
37 | this.$data.__open = !this.$data.__open;
38 | },
39 | "@mouseleave"() {
40 | this.$data.__close();
41 | },
42 | });
43 | }
44 |
45 | function handleContent(el, Alpine) {
46 | Alpine.bind(el, {
47 | "x-show"() {
48 | return this.$data.__open;
49 | },
50 | // "x-anchor"() {
51 | // return document.getElementById(
52 | // this.$id("alpine-hover-card-trigger"),
53 | // );
54 | // },
55 | });
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Pest.php:
--------------------------------------------------------------------------------
1 | extend(Tests\TestCase::class)
15 | // ->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
16 | ->in('Feature');
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Expectations
21 | |--------------------------------------------------------------------------
22 | |
23 | | When you're writing tests, you often need to check that values meet certain conditions. The
24 | | "expect()" function gives you access to a set of "expectations" methods that you can use
25 | | to assert different things. Of course, you may extend the Expectation API at any time.
26 | |
27 | */
28 |
29 | expect()->extend('toBeOne', function () {
30 | return $this->toBe(1);
31 | });
32 |
33 | /*
34 | |--------------------------------------------------------------------------
35 | | Functions
36 | |--------------------------------------------------------------------------
37 | |
38 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your
39 | | project that you don't want to repeat in every file. Here you can also expose helpers as
40 | | global functions to help you to reduce the number of lines of code in your test files.
41 | |
42 | */
43 |
44 | function something()
45 | {
46 | // ..
47 | }
48 |
--------------------------------------------------------------------------------
/database/migrations/0001_01_01_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('name');
17 | $table->string('email')->unique();
18 | $table->timestamp('email_verified_at')->nullable();
19 | $table->string('password');
20 | $table->boolean('is_root_user')->default(false);
21 | $table->rememberToken();
22 | $table->timestamps();
23 | });
24 |
25 | Schema::create('password_reset_tokens', function (Blueprint $table) {
26 | $table->string('email')->primary();
27 | $table->string('token');
28 | $table->timestamp('created_at')->nullable();
29 | });
30 |
31 | Schema::create('sessions', function (Blueprint $table) {
32 | $table->string('id')->primary();
33 | $table->foreignId('user_id')->nullable()->index();
34 | $table->string('ip_address', 45)->nullable();
35 | $table->text('user_agent')->nullable();
36 | $table->longText('payload');
37 | $table->integer('last_activity')->index();
38 | });
39 | }
40 |
41 | /**
42 | * Reverse the migrations.
43 | */
44 | public function down(): void
45 | {
46 | Schema::dropIfExists('users');
47 | Schema::dropIfExists('password_reset_tokens');
48 | Schema::dropIfExists('sessions');
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/app/Console/Commands/Init.php:
--------------------------------------------------------------------------------
1 | info('Initializing application...');
17 |
18 | if ($this->option('production')) {
19 | if (config('database.default') == 'sqlite') {
20 | $this->createDatabase();
21 | }
22 |
23 | return;
24 | }
25 | $this->createLogFile();
26 | $this->createDatabase();
27 | $this->migrate();
28 | Artisan::call('optimize:clear');
29 |
30 | }
31 |
32 | private function createLogFile()
33 | {
34 | $this->info('Creating laravel.log file...');
35 | if (! file_exists(storage_path('logs/laravel.log'))) {
36 | touch(storage_path('logs/laravel.log'));
37 | }
38 | }
39 |
40 | private function createDatabase()
41 | {
42 | $dbPath = config('database.connections.sqlite.database');
43 | $this->info("Database path: {$dbPath}");
44 | $dbDir = dirname($dbPath);
45 |
46 | if (! file_exists($dbDir)) {
47 | mkdir($dbDir, 0755, true);
48 | $this->info("Created database directory: {$dbDir}");
49 | }
50 |
51 | if (! file_exists($dbPath)) {
52 | touch($dbPath);
53 | chmod($dbPath, 0755);
54 | $this->info("Created database file: {$dbPath}");
55 | }
56 | }
57 |
58 | private function migrate()
59 | {
60 | $this->info('Migrating database...');
61 | Artisan::call('migrate');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/Services/ButtonCvaService.php:
--------------------------------------------------------------------------------
1 | [
17 | 'variant' => [
18 | 'default' => 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
19 | 'destructive' => 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
20 | 'outline' => 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground text-foreground',
21 | 'secondary' => 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
22 | 'ghost' => 'hover:bg-accent hover:text-accent-foreground',
23 | 'link' => 'text-primary underline-offset-4 hover:underline',
24 | ],
25 | 'size' => [
26 | 'default' => 'h-9 px-4 py-2',
27 | 'sm' => 'h-8 rounded-md px-3 text-xs',
28 | 'lg' => 'h-10 rounded-md px-8',
29 | 'icon' => 'h-9 w-9',
30 | ],
31 | ],
32 | 'defaultVariants' => [
33 | 'variant' => 'default',
34 | 'size' => 'default',
35 | ],
36 | ],
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/database/seeders/TodoSeeder.php:
--------------------------------------------------------------------------------
1 | 'Test todo',
18 | // 'status' => 'backlog',
19 | // 'user_id' => 1,
20 | // 'worked_at' => null,
21 | // ]);
22 | // });
23 | // });
24 | // }
25 |
26 | // // Dump todos from yesterday
27 | // for ($i = 0; $i < 3; $i++) {
28 | // Todo::unguarded(function () {
29 | // Todo::withoutEvents(function () {
30 | // Todo::create([
31 | // 'title' => 'Todo from yesterday',
32 | // 'status' => 'backlog',
33 | // 'user_id' => 1,
34 | // 'worked_at' => now()->subDays(1),
35 | // ]);
36 | // });
37 | // });
38 | // }
39 | // // Today todos
40 | // for ($i = 0; $i < 3; $i++) {
41 | // Todo::unguarded(function () {
42 | // Todo::withoutEvents(function () {
43 | // Todo::create([
44 | // 'title' => 'Must done today',
45 | // 'status' => 'backlog',
46 | // 'user_id' => 1,
47 | // 'worked_at' => now(),
48 | // ]);
49 | // });
50 | // });
51 | // }
52 |
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/Actions/Fortify/UpdateUserProfileInformation.php:
--------------------------------------------------------------------------------
1 | $input
17 | */
18 | public function update(User $user, array $input): void
19 | {
20 | Validator::make($input, [
21 | 'name' => ['required', 'string', 'max:255'],
22 |
23 | 'email' => [
24 | 'required',
25 | 'string',
26 | 'email',
27 | 'max:255',
28 | Rule::unique('users')->ignore($user->id),
29 | ],
30 | ])->validateWithBag('updateProfileInformation');
31 |
32 | if ($input['email'] !== $user->email &&
33 | $user instanceof MustVerifyEmail) {
34 | $this->updateVerifiedUser($user, $input);
35 | } else {
36 | $user->forceFill([
37 | 'name' => $input['name'],
38 | 'email' => $input['email'],
39 | ])->save();
40 | }
41 | }
42 |
43 | /**
44 | * Update the given verified user's profile information.
45 | *
46 | * @param array $input
47 | */
48 | protected function updateVerifiedUser(User $user, array $input): void
49 | {
50 | $user->forceFill([
51 | 'name' => $input['name'],
52 | 'email' => $input['email'],
53 | 'email_verified_at' => null,
54 | ])->save();
55 |
56 | $user->sendEmailVerificationNotification();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/Livewire/Auth/Login.php:
--------------------------------------------------------------------------------
1 | 'Email is required', 'email.email' => 'Invalid email'])]
16 | public string $email;
17 |
18 | #[Validate(['required'], ['password.required' => 'Password is required'])]
19 | public string $password;
20 |
21 | public bool $isRegistrationEnabled = true;
22 |
23 | public function mount(InstanceSettings $settings)
24 | {
25 | if (User::count() === 0) {
26 | return redirect()->route('register');
27 | }
28 | $this->isRegistrationEnabled = $settings->is_registration_enabled;
29 |
30 | if (auth()->check()) {
31 | return redirect()->intended(route('dashboard'));
32 | }
33 | if (config('app.env') !== 'production') {
34 | $this->email = 'test@example.com';
35 | $this->password = 'password';
36 | }
37 | }
38 |
39 | public function login()
40 | {
41 | try {
42 | $this->validate();
43 | $response = auth()->attempt([
44 | 'email' => $this->email,
45 | 'password' => $this->password,
46 | ]);
47 | if ($response) {
48 | return redirect()->intended(route('dashboard'));
49 | }
50 |
51 | $this->addError('password', 'Invalid credentials');
52 | } catch (\Exception $e) {
53 | $this->addError('password', "Something went wrong: {$e->getMessage()}");
54 | }
55 | }
56 |
57 | public function render()
58 | {
59 | return view('livewire.auth.login');
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/resources/views/vendor/tall-toasts/livewire/toasts.blade.php:
--------------------------------------------------------------------------------
1 |
10 |
12 |
13 |
28 | @include('tall-toasts::includes.content')
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/database/migrations/0001_01_01_000002_create_jobs_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('queue')->index();
17 | $table->longText('payload');
18 | $table->unsignedTinyInteger('attempts');
19 | $table->unsignedInteger('reserved_at')->nullable();
20 | $table->unsignedInteger('available_at');
21 | $table->unsignedInteger('created_at');
22 | });
23 |
24 | Schema::create('job_batches', function (Blueprint $table) {
25 | $table->string('id')->primary();
26 | $table->string('name');
27 | $table->integer('total_jobs');
28 | $table->integer('pending_jobs');
29 | $table->integer('failed_jobs');
30 | $table->longText('failed_job_ids');
31 | $table->mediumText('options')->nullable();
32 | $table->integer('cancelled_at')->nullable();
33 | $table->integer('created_at');
34 | $table->integer('finished_at')->nullable();
35 | });
36 |
37 | Schema::create('failed_jobs', function (Blueprint $table) {
38 | $table->id();
39 | $table->string('uuid')->unique();
40 | $table->text('connection');
41 | $table->text('queue');
42 | $table->longText('payload');
43 | $table->longText('exception');
44 | $table->timestamp('failed_at')->useCurrent();
45 | });
46 | }
47 |
48 | /**
49 | * Reverse the migrations.
50 | */
51 | public function down(): void
52 | {
53 | Schema::dropIfExists('jobs');
54 | Schema::dropIfExists('job_batches');
55 | Schema::dropIfExists('failed_jobs');
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/app/Providers/FortifyServiceProvider.php:
--------------------------------------------------------------------------------
1 | view('livewire.auth.login'));
31 | Fortify::registerView(fn () => view('livewire.auth.register'));
32 |
33 | Fortify::authenticateUsing(function (Request $request) {
34 | $user = User::where('email', $request->email)->first();
35 |
36 | if ($user &&
37 | Hash::check($request->password, $user->password)) {
38 | return $user;
39 | }
40 | });
41 |
42 | Fortify::createUsersUsing(CreateNewUser::class);
43 | Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
44 | Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
45 | Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
46 |
47 | RateLimiter::for('login', function (Request $request) {
48 | $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());
49 |
50 | return Limit::perMinute(5)->by($throttleKey);
51 | });
52 |
53 | RateLimiter::for('two-factor', function (Request $request) {
54 | return Limit::perMinute(5)->by($request->session()->get('login.id'));
55 | });
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/Services/DialogCvaService.php:
--------------------------------------------------------------------------------
1 | [
17 | 'variant' => [
18 | 'dialog' => 'transition-[translate,opacity,scale,overlay,display] w-full max-w-lg border bg-background p-6 shadow-lg sm:rounded-lg open:animate-in animate-out',
19 | 'sheet' => 'fixed grid grid-rows-[auto_1fr_auto] m-0 gap-4 bg-background p-6 shadow-lg transition-[display,overlay,transform] ease-in-out duration-300',
20 | ],
21 | 'side' => [
22 | 'top' => 'inset-x-0 top-0 max-w-full min-w-full overflow-x-auto !mb-auto border-b -translate-y-full open:translate-y-0 [@starting-style]:open:-translate-y-full',
23 | 'bottom' => 'inset-x-0 bottom-0 max-w-full min-w-full overflow-x-auto !mt-auto border-t translate-y-full open:translate-y-0 [@starting-style]:open:translate-y-full',
24 | 'left' => 'inset-y-0 left-0 max-h-dvh min-h-dvh overflow-y-auto !mr-auto w-3/4 border-r sm:max-w-sm -translate-x-full open:translate-x-0 [@starting-style]:open:-translate-x-full',
25 | 'right' => 'inset-y-0 right-0 max-h-dvh min-h-dvh overflow-y-auto !ml-auto w-3/4 border-l sm:max-w-sm translate-x-full open:translate-x-0 [@starting-style]:open:translate-x-full',
26 | 'center' => 'open:fade-in-0 open:zoom-in-95 fade-out-0 zoom-out-95 ',
27 | ],
28 | ],
29 | 'defaultVariants' => [
30 | 'side' => 'center',
31 | 'variant' => 'dialog',
32 | ],
33 | ],
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/resources/views/livewire/forms/todo-input.blade.php:
--------------------------------------------------------------------------------
1 |
62 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/app/Livewire/Forms/TodoInput.php:
--------------------------------------------------------------------------------
1 | title = $title;
37 | $this->isDump = $isDump;
38 | $this->mode = $mode;
39 | $this->todoId = $todoId;
40 | $this->target = $target;
41 | $this->autoSaveEnabled = $autoSaveEnabled;
42 | }
43 |
44 | public function getListeners()
45 | {
46 | return [
47 | 'todo-saved' => 'resetInput',
48 | ];
49 | }
50 |
51 | public function resetInput()
52 | {
53 | if ($this->mode === 'create') {
54 | $this->title = '';
55 | }
56 | }
57 |
58 | public function handleSubmit()
59 | {
60 | try {
61 | $this->validate();
62 | if ($this->mode === 'edit' && $this->todoId) {
63 | $todo = Todo::getOwnTodo($this->todoId);
64 | $todo->title = $this->title;
65 | $todo->save();
66 | } else {
67 | Todo::create([
68 | 'title' => $this->title,
69 | 'worked_at' => $this->isDump ? null : now(),
70 | ]);
71 | }
72 | $this->dispatch('hashtags-updated');
73 | $this->dispatch('todos-updated');
74 | $this->resetInput();
75 | } catch (\Exception $e) {
76 | toast()->danger($e->getMessage())->push();
77 | }
78 | }
79 |
80 | public function render()
81 | {
82 | return view('livewire.forms.todo-input');
83 | }
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/app/Livewire/Dump.php:
--------------------------------------------------------------------------------
1 | id;
31 |
32 | return [
33 | "echo-private:user.{$userId},TodoUpdated" => 'refreshTodos',
34 | 'todos-updated' => 'refreshTodos',
35 | ];
36 | }
37 |
38 | public function updateTitle($title)
39 | {
40 | $this->title = $title;
41 | }
42 |
43 | public function mount()
44 | {
45 | $this->refreshTodos();
46 | }
47 |
48 | public function refreshTodos()
49 | {
50 | $this->todos = Todo::getAllTodosExceptToday()->where('status', '!=', 'completed');
51 | }
52 |
53 | public function addTodo()
54 | {
55 | try {
56 | $this->validate();
57 | Auth::user()->todos()->create([
58 | 'title' => $this->title,
59 | 'worked_at' => null,
60 | ]);
61 | $this->title = '';
62 | } catch (\Exception $e) {
63 | toast()->danger($e->getMessage())->push();
64 | }
65 | }
66 |
67 | public function addToToday($id)
68 | {
69 | try {
70 | $todo = Todo::getOwnTodo($id);
71 | $todo->worked_at = now();
72 | $todo->save();
73 | $this->refreshTodos();
74 | } catch (\Exception $e) {
75 | toast()->danger($e->getMessage())->push();
76 | }
77 | }
78 |
79 | public function deleteTodo($id)
80 | {
81 | try {
82 | $todo = Todo::getOwnTodo($id);
83 | $todo->delete();
84 | $this->refreshTodos();
85 | $this->dispatch('todo-saved');
86 | } catch (\Exception $e) {
87 | toast()->danger($e->getMessage())->push();
88 | }
89 | }
90 |
91 | public function render()
92 | {
93 | return view('livewire.dump');
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/resources/views/livewire/dump.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Add todo
6 |
7 |
8 | @forelse ($todos as $todo)
9 |
35 | @empty
36 |
No tasks. Nice work!
37 | @endforelse
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/resources/views/components/navigation.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{-- --}}
12 | {{ strtoupper(auth()->user()->name[0]) }}
13 |
14 |
15 |
16 |
17 |
18 | {{ auth()->user()->name }}
19 | @if (auth()->user()->is_root_user)
20 | (root)
21 | @endif
22 |
23 | {{ auth()->user()->email }}
25 |
26 |
27 | Dashboard
29 | @if ($isPaymentEnabled && $isCloudEnabled)
30 | Billing
32 | @endif
33 |
34 |
40 |
41 | @if ($isRootUser)
42 |
43 |
44 | Instance Settings
46 | @endif
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/config/filesystems.php:
--------------------------------------------------------------------------------
1 | env('FILESYSTEM_DISK', 'local'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Filesystem Disks
21 | |--------------------------------------------------------------------------
22 | |
23 | | Below you may configure as many filesystem disks as necessary, and you
24 | | may even configure multiple disks for the same driver. Examples for
25 | | most supported storage drivers are configured here for reference.
26 | |
27 | | Supported drivers: "local", "ftp", "sftp", "s3"
28 | |
29 | */
30 |
31 | 'disks' => [
32 |
33 | 'local' => [
34 | 'driver' => 'local',
35 | 'root' => storage_path('app/private'),
36 | 'serve' => true,
37 | 'throw' => false,
38 | ],
39 |
40 | 'public' => [
41 | 'driver' => 'local',
42 | 'root' => storage_path('app/public'),
43 | 'url' => env('APP_URL').'/storage',
44 | 'visibility' => 'public',
45 | 'throw' => false,
46 | ],
47 |
48 | 's3' => [
49 | 'driver' => 's3',
50 | 'key' => env('AWS_ACCESS_KEY_ID'),
51 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
52 | 'region' => env('AWS_DEFAULT_REGION'),
53 | 'bucket' => env('AWS_BUCKET'),
54 | 'url' => env('AWS_URL'),
55 | 'endpoint' => env('AWS_ENDPOINT'),
56 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
57 | 'throw' => false,
58 | ],
59 |
60 | ],
61 |
62 | /*
63 | |--------------------------------------------------------------------------
64 | | Symbolic Links
65 | |--------------------------------------------------------------------------
66 | |
67 | | Here you may configure the symbolic links that will be created when the
68 | | `storage:link` Artisan command is executed. The array keys should be
69 | | the locations of the links and the values should be their targets.
70 | |
71 | */
72 |
73 | 'links' => [
74 | public_path('storage') => storage_path('app/public'),
75 | ],
76 |
77 | ];
78 |
--------------------------------------------------------------------------------
/resources/views/components/input/index.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'type' => 'text',
3 | 'timeout' => 2000,
4 | 'copy' => true,
5 | 'target' => null,
6 | ])
7 |
8 | only(['class'])->filter(fn($class) => str_contains($class, 'col-span') || str_contains($class, 'row-span'))->merge(['class' => 'relative w-full']) }}
9 | x-data="{
10 | showPassword: false,
11 | inputType: '{{ $type }}',
12 | copied: false,
13 | target: '{{ $target }}'
14 | }">
15 |
twMerge('flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 dark:bg-input-background file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 read-only:cursor-not-allowed read-only:opacity-50 text-foreground') }} />
17 | @if ($target)
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | @endif
27 | @if ($type === 'password')
28 |
37 | @endif
38 | @if ($copy === true)
39 | @if ($type !== 'password' && $type !== 'email')
40 |
47 | @endif
48 | @endif
49 |
50 |
--------------------------------------------------------------------------------
/config/broadcasting.php:
--------------------------------------------------------------------------------
1 | env('BROADCAST_CONNECTION', 'reverb'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Broadcast Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the broadcast connections that will be used
26 | | to broadcast events to other systems or over WebSockets. Samples of
27 | | each available type of connection are provided inside this array.
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'reverb' => [
34 | 'driver' => 'reverb',
35 | 'key' => env('REVERB_APP_KEY'),
36 | 'secret' => env('REVERB_APP_SECRET'),
37 | 'app_id' => env('REVERB_APP_ID'),
38 | 'options' => [
39 | 'host' => env('REVERB_HOST'),
40 | 'port' => env('REVERB_PORT', 443),
41 | 'scheme' => env('REVERB_SCHEME', 'https'),
42 | 'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
43 | ],
44 | 'client_options' => [
45 | // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
46 | ],
47 | ],
48 |
49 | 'pusher' => [
50 | 'driver' => 'pusher',
51 | 'key' => env('PUSHER_APP_KEY'),
52 | 'secret' => env('PUSHER_APP_SECRET'),
53 | 'app_id' => env('PUSHER_APP_ID'),
54 | 'options' => [
55 | 'cluster' => env('PUSHER_APP_CLUSTER'),
56 | 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
57 | 'port' => env('PUSHER_PORT', 443),
58 | 'scheme' => env('PUSHER_SCHEME', 'https'),
59 | 'encrypted' => true,
60 | 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
61 | ],
62 | 'client_options' => [
63 | // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
64 | ],
65 | ],
66 |
67 | 'ably' => [
68 | 'driver' => 'ably',
69 | 'key' => env('ABLY_KEY'),
70 | ],
71 |
72 | 'log' => [
73 | 'driver' => 'log',
74 | ],
75 |
76 | 'null' => [
77 | 'driver' => 'null',
78 | ],
79 |
80 | ],
81 |
82 | ];
83 |
--------------------------------------------------------------------------------
/app/Livewire/Auth/Register.php:
--------------------------------------------------------------------------------
1 | 'Email is required', 'email.email' => 'Invalid email'])]
16 | public string $email;
17 |
18 | #[Validate(['required'], ['password.required' => 'Password is required'])]
19 | public string $password;
20 |
21 | #[Validate(['required'], ['password_confirmation.required' => 'Password confirmation is required'])]
22 | public string $password_confirmation;
23 |
24 | #[Locked]
25 | public bool $isRootUser = false;
26 |
27 | public function mount(InstanceSettings $instanceSettings)
28 | {
29 | if (User::count() === 0) {
30 | $this->isRootUser = true;
31 | }
32 | if ($instanceSettings->is_registration_enabled === false) {
33 | return redirect()->route('dashboard');
34 | }
35 | if (config('app.env') !== 'production') {
36 | $this->email = 'test@example.com';
37 | $this->password = 'password';
38 | $this->password_confirmation = 'password';
39 | }
40 | }
41 |
42 | public function register(InstanceSettings $instanceSettings)
43 | {
44 | try {
45 | $this->validate();
46 | $emailExists = User::where('email', $this->email)->exists();
47 | if ($emailExists) {
48 | throw ValidationException::withMessages([
49 | 'email' => 'Email already exists',
50 | ]);
51 | }
52 | if ($this->password !== $this->password_confirmation) {
53 | throw ValidationException::withMessages([
54 | 'password_confirmation' => 'Password confirmation does not match',
55 | ]);
56 | }
57 |
58 | $user = User::create([
59 | 'email' => $this->email,
60 | 'password' => Hash::make($this->password),
61 | 'is_root_user' => $this->isRootUser,
62 | ]);
63 | if ($this->isRootUser) {
64 | $instanceSettings->is_registration_enabled = false;
65 | $instanceSettings->save();
66 | }
67 |
68 | auth()->login($user);
69 |
70 | return redirect()->intended(route('dashboard'));
71 | } catch (\Exception $e) {
72 | if ($e instanceof ValidationException) {
73 | $this->addError('email', $e->getMessage());
74 | } else {
75 | $this->addError('password_confirmation', "Something went wrong: {$e->getMessage()}");
76 | }
77 | }
78 | }
79 |
80 | public function render()
81 | {
82 | return view('livewire.auth.register');
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/resources/js/plugins/accordion.js:
--------------------------------------------------------------------------------
1 | export default function (Alpine) {
2 | Alpine.directive("accordion", (el, directive) => {
3 | if (!directive.value) handleRoot(el, Alpine);
4 | else if (directive.value === "item") handleItem(el, Alpine);
5 | else if (directive.value === "trigger") handleTrigger(el, Alpine);
6 | else if (directive.value === "content") handleContent(el, Alpine);
7 | }).before("bind");
8 | }
9 |
10 | function handleRoot(el, Alpine) {
11 | const active = el.getAttribute("defaultValue")
12 | ? [el.getAttribute("defaultValue")]
13 | : [];
14 | const multiple = el.getAttribute("type") === "multiple" ? true : false;
15 | // prettier-ignore
16 | const collapsible = el.getAttribute("collapsible") === "collapsible" ? true : false;
17 |
18 | Alpine.bind(el, {
19 | "x-id"() {
20 | return ["alpine-accordion"];
21 | },
22 | ":id"() {
23 | return this.$id("alpine-accordion");
24 | },
25 | "x-data"() {
26 | return {
27 | __multiple: multiple,
28 | __collapsible: collapsible,
29 | __active: active,
30 |
31 | __isOpen(item) {
32 | return this.__active.includes(item);
33 | },
34 |
35 | __toggle(item) {
36 | if (
37 | this.__isOpen(item) &&
38 | (this.__collapsible || this.__multiple)
39 | ) {
40 | return this.__remove(item);
41 | }
42 |
43 | this.__add(item);
44 | },
45 |
46 | __add(item) {
47 | if (this.__multiple) {
48 | return this.__active.push(item);
49 | }
50 |
51 | this.__active = [item];
52 | },
53 |
54 | __remove(item) {
55 | if (this.__multiple) {
56 | return (this.__active = this.__active.filter(
57 | (activeItem) => activeItem !== item,
58 | ));
59 | }
60 |
61 | this.__active = [];
62 | },
63 |
64 | __getDataState(item) {
65 | return this.__isOpen(item) ? "open" : "closed";
66 | },
67 | };
68 | },
69 | });
70 | }
71 |
72 | function handleItem(el, Alpine) {
73 | Alpine.bind(el, {
74 | // "x-show"() {
75 | // return this.$data.__open;
76 | // },
77 | });
78 | }
79 |
80 | function handleTrigger(el, Alpine) {
81 | Alpine.bind(el, {
82 | "x-ref": "__accordion-trigger",
83 | ":id"() {
84 | return this.$id("alpine-accordion-trigger");
85 | },
86 | "@click"() {
87 | this.$data.__open = !this.$data.__open;
88 | },
89 | });
90 | }
91 |
92 | function handleContent(el, Alpine) {
93 | Alpine.bind(el, {
94 | "x-show"() {
95 | return this.$data.__open;
96 | },
97 | });
98 | }
99 |
--------------------------------------------------------------------------------
/resources/js/plugins/form.js:
--------------------------------------------------------------------------------
1 | export default function (Alpine) {
2 | Alpine.directive("form", (el, directive) => {
3 | if (!directive.value) handleRoot(el, Alpine);
4 | else if (directive.value === "item") handleItem(el, Alpine);
5 | else if (directive.value === "label") handleLabel(el, Alpine);
6 | else if (directive.value === "control") handleControl(el, Alpine);
7 | else if (directive.value === "description")
8 | handleDescription(el, Alpine);
9 | else if (directive.value === "message")
10 | handleMessage(el, Alpine, directive.modifiers);
11 | }).before("bind");
12 | }
13 |
14 | function handleRoot(el, Alpine) {}
15 |
16 | function handleItem(el, Alpine) {
17 | Alpine.bind(el, () => {
18 | return {
19 | "x-id"() {
20 | return [
21 | "form-item",
22 | "form-item-control",
23 | "form-item-description",
24 | "form-item-message",
25 | ];
26 | },
27 | "x-data"() {
28 | return {
29 | __error: false,
30 | init() {
31 | this.__error = Alpine.extractProp(el, "error", false);
32 | },
33 | };
34 | },
35 | ":id"() {
36 | return this.$id("form-item");
37 | },
38 | };
39 | });
40 | }
41 |
42 | function handleLabel(el, Alpine) {
43 | Alpine.bind(el, () => {
44 | return {
45 | ":for"() {
46 | return this.$id("form-item-control");
47 | },
48 | };
49 | });
50 | }
51 |
52 | function handleControl(el, Alpine) {
53 | Alpine.bind(el, () => {
54 | return {
55 | ":id"() {
56 | return this.$id("form-item-control");
57 | },
58 | ":aria-invalid"() {
59 | return this.$data.__error;
60 | },
61 | ":aria-describedby"() {
62 | return this.$id("form-item-description");
63 | },
64 | ":aria-errormessage"() {
65 | return this.$id("form-item-message");
66 | },
67 | };
68 | });
69 | }
70 |
71 | function handleDescription(el, Alpine) {
72 | Alpine.bind(el, () => {
73 | return {
74 | ":id"() {
75 | return this.$id("form-item-description");
76 | },
77 | };
78 | });
79 | }
80 |
81 | function handleMessage(el, Alpine, modifiers) {
82 | Alpine.bind(el, () => {
83 | return {
84 | ":id"() {
85 | return this.$id("form-item-message");
86 | },
87 | ":aria-live"() {
88 | return getAriaLive(modifiers);
89 | },
90 | };
91 | });
92 | }
93 |
94 | function getAriaLive(modifiers) {
95 | if (!modifiers.length) return "off";
96 |
97 | let types = ["polite", "assertive", "off"];
98 | return types.find((i) => modifiers.includes(i));
99 | }
100 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel/laravel",
3 | "type": "project",
4 | "description": "The skeleton application for the Laravel framework.",
5 | "keywords": [
6 | "laravel",
7 | "framework"
8 | ],
9 | "license": "MIT",
10 | "require": {
11 | "php": "^8.3",
12 | "fakerphp/faker": "1.24.0",
13 | "feature-ninja/cva": "^0.3.0",
14 | "gehrisandro/tailwind-merge-laravel": "^1.2",
15 | "inertiajs/inertia-laravel": "^2.0",
16 | "laravel/cashier": "^15.6",
17 | "laravel/fortify": "1.24.5",
18 | "laravel/framework": "11.33.2",
19 | "laravel/horizon": "5.29.3",
20 | "laravel/reverb": "1.4.3",
21 | "laravel/tinker": "2.10.0",
22 | "league/commonmark": "^2.6",
23 | "livewire/livewire": "3.5.12",
24 | "mallardduck/blade-lucide-icons": "^1.23",
25 | "opcodesio/log-viewer": "3.12.0",
26 | "silviolleite/laravelpwa": "^2.0",
27 | "spatie/laravel-settings": "^3.4",
28 | "stripe/stripe-php": "16.2.0",
29 | "tightenco/ziggy": "^2.5",
30 | "usernotnull/tall-toasts": "^2.1"
31 | },
32 | "require-dev": {
33 | "laravel/pail": "1.2.1",
34 | "laravel/pint": "1.18.2",
35 | "laravel/sail": "1.38.0",
36 | "mockery/mockery": "1.6.12",
37 | "nunomaduro/collision": "8.5.0",
38 | "pestphp/pest": "3.5.1",
39 | "pestphp/pest-plugin-laravel": "3.0.0"
40 | },
41 | "autoload": {
42 | "psr-4": {
43 | "App\\": "app/",
44 | "Database\\Factories\\": "database/factories/",
45 | "Database\\Seeders\\": "database/seeders/"
46 | }
47 | },
48 | "autoload-dev": {
49 | "psr-4": {
50 | "Tests\\": "tests/"
51 | }
52 | },
53 | "scripts": {
54 | "post-autoload-dump": [
55 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
56 | "@php artisan package:discover --ansi"
57 | ],
58 | "post-update-cmd": [
59 | "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
60 | ],
61 | "post-root-package-install": [
62 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
63 | ],
64 | "post-create-project-cmd": [
65 | "@php artisan key:generate --ansi",
66 | "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
67 | "@php artisan migrate --graceful --ansi"
68 | ],
69 | "dev": [
70 | "Composer\\Config::disableProcessTimeout",
71 | "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan reverb:start\" \"php artisan serve --host=0.0.0.0\" \"php artisan queue:listen\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=reverb,server,queue,logs,vite"
72 | ]
73 | },
74 | "extra": {
75 | "laravel": {
76 | "dont-discover": []
77 | }
78 | },
79 | "config": {
80 | "optimize-autoloader": true,
81 | "preferred-install": "dist",
82 | "sort-packages": true,
83 | "allow-plugins": {
84 | "pestphp/pest-plugin": true,
85 | "php-http/discovery": true
86 | }
87 | },
88 | "minimum-stability": "stable",
89 | "prefer-stable": true
90 | }
91 |
--------------------------------------------------------------------------------
/config/settings.php:
--------------------------------------------------------------------------------
1 | [
12 | InstanceSettings::class,
13 | ],
14 |
15 | /*
16 | * The path where the settings classes will be created.
17 | */
18 | 'setting_class_path' => app_path('Settings'),
19 |
20 | /*
21 | * In these directories settings migrations will be stored and ran when migrating. A settings
22 | * migration created via the make:settings-migration command will be stored in the first path or
23 | * a custom defined path when running the command.
24 | */
25 | 'migrations_paths' => [
26 | database_path('settings'),
27 | ],
28 |
29 | /*
30 | * When no repository was set for a settings class the following repository
31 | * will be used for loading and saving settings.
32 | */
33 | 'default_repository' => 'database',
34 |
35 | /*
36 | * Settings will be stored and loaded from these repositories.
37 | */
38 | 'repositories' => [
39 | 'database' => [
40 | 'type' => Spatie\LaravelSettings\SettingsRepositories\DatabaseSettingsRepository::class,
41 | 'model' => null,
42 | 'table' => null,
43 | 'connection' => null,
44 | ],
45 | 'redis' => [
46 | 'type' => Spatie\LaravelSettings\SettingsRepositories\RedisSettingsRepository::class,
47 | 'connection' => null,
48 | 'prefix' => null,
49 | ],
50 | ],
51 |
52 | /*
53 | * The encoder and decoder will determine how settings are stored and
54 | * retrieved in the database. By default, `json_encode` and `json_decode`
55 | * are used.
56 | */
57 | 'encoder' => null,
58 | 'decoder' => null,
59 |
60 | /*
61 | * The contents of settings classes can be cached through your application,
62 | * settings will be stored within a provided Laravel store and can have an
63 | * additional prefix.
64 | */
65 | 'cache' => [
66 | 'enabled' => env('SETTINGS_CACHE_ENABLED', false),
67 | 'store' => null,
68 | 'prefix' => null,
69 | 'ttl' => null,
70 | ],
71 |
72 | /*
73 | * These global casts will be automatically used whenever a property within
74 | * your settings class isn't a default PHP type.
75 | */
76 | 'global_casts' => [
77 | DateTimeInterface::class => Spatie\LaravelSettings\SettingsCasts\DateTimeInterfaceCast::class,
78 | DateTimeZone::class => Spatie\LaravelSettings\SettingsCasts\DateTimeZoneCast::class,
79 | // Spatie\DataTransferObject\DataTransferObject::class => Spatie\LaravelSettings\SettingsCasts\DtoCast::class,
80 | Spatie\LaravelData\Data::class => Spatie\LaravelSettings\SettingsCasts\DataCast::class,
81 | ],
82 |
83 | /*
84 | * The package will look for settings in these paths and automatically
85 | * register them.
86 | */
87 | 'auto_discover_settings' => [
88 | app_path('Settings'),
89 | ],
90 |
91 | /*
92 | * Automatically discovered settings classes can be cached, so they don't
93 | * need to be searched each time the application boots up.
94 | */
95 | 'discovered_settings_cache_path' => base_path('bootstrap/cache'),
96 | ];
97 |
--------------------------------------------------------------------------------
/resources/views/vendor/tall-toasts/includes/icon.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
33 |
34 |
35 |
36 |
44 |
45 |
--------------------------------------------------------------------------------
/config/reverb.php:
--------------------------------------------------------------------------------
1 | env('REVERB_SERVER', 'reverb'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Reverb Servers
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may define details for each of the supported Reverb servers.
24 | | Each server has its own configuration options that are defined in
25 | | the array below. You should ensure all the options are present.
26 | |
27 | */
28 |
29 | 'servers' => [
30 |
31 | 'reverb' => [
32 | 'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
33 | 'port' => env('REVERB_SERVER_PORT', 8080),
34 | 'hostname' => env('REVERB_HOST'),
35 | 'options' => [
36 | 'tls' => [],
37 | ],
38 | 'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000),
39 | 'scaling' => [
40 | 'enabled' => env('REVERB_SCALING_ENABLED', false),
41 | 'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
42 | 'server' => [
43 | 'url' => env('REDIS_URL'),
44 | 'host' => env('REDIS_HOST', '127.0.0.1'),
45 | 'port' => env('REDIS_PORT', '6379'),
46 | 'username' => env('REDIS_USERNAME'),
47 | 'password' => env('REDIS_PASSWORD'),
48 | 'database' => env('REDIS_DB', '0'),
49 | ],
50 | ],
51 | 'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
52 | 'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15),
53 | ],
54 |
55 | ],
56 |
57 | /*
58 | |--------------------------------------------------------------------------
59 | | Reverb Applications
60 | |--------------------------------------------------------------------------
61 | |
62 | | Here you may define how Reverb applications are managed. If you choose
63 | | to use the "config" provider, you may define an array of apps which
64 | | your server will support, including their connection credentials.
65 | |
66 | */
67 |
68 | 'apps' => [
69 |
70 | 'provider' => 'config',
71 |
72 | 'apps' => [
73 | [
74 | 'key' => env('REVERB_APP_KEY'),
75 | 'secret' => env('REVERB_APP_SECRET'),
76 | 'app_id' => env('REVERB_APP_ID'),
77 | 'options' => [
78 | 'host' => env('REVERB_HOST'),
79 | 'port' => env('REVERB_PORT', 443),
80 | 'scheme' => env('REVERB_SCHEME', 'https'),
81 | 'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
82 | ],
83 | 'allowed_origins' => ['*'],
84 | 'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
85 | 'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30),
86 | 'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000),
87 | ],
88 | ],
89 |
90 | ],
91 |
92 | ];
93 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | import forms from "@tailwindcss/forms";
2 | import defaultTheme from "tailwindcss/defaultTheme";
3 |
4 | /** @type {import('tailwindcss').Config} */
5 | export default {
6 | darkMode: "class",
7 | content: [
8 | "./vendor/usernotnull/tall-toasts/config/**/*.php",
9 | "./vendor/usernotnull/tall-toasts/resources/views/**/*.blade.php",
10 | "./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php",
11 | "./storage/framework/views/*.php",
12 | "./resources/views/**/*.blade.php",
13 | "./app/View/Components/**/*.php",
14 | "./app/Services/**/*.php",
15 | ],
16 |
17 | theme: {
18 | container: {
19 | center: true,
20 | padding: "2rem",
21 | screens: {
22 | "2xl": "1400px",
23 | },
24 | },
25 | extend: {
26 | colors: {
27 | "input-background": "hsl(var(--input-background))",
28 | "warning": "hsl(var(--warning))",
29 | "success": "hsl(var(--success))",
30 | "info": "hsl(var(--info))",
31 | border: "hsl(var(--border))",
32 | input: "hsl(var(--input))",
33 | ring: "hsl(var(--ring))",
34 | background: "hsl(var(--background))",
35 | foreground: "hsl(var(--foreground))",
36 | primary: {
37 | DEFAULT: "hsl(var(--primary))",
38 | foreground: "hsl(var(--primary-foreground))",
39 | },
40 | secondary: {
41 | DEFAULT: "hsl(var(--secondary))",
42 | foreground: "hsl(var(--secondary-foreground))",
43 | },
44 | destructive: {
45 | DEFAULT: "hsl(var(--destructive))",
46 | foreground: "hsl(var(--destructive-foreground))",
47 | },
48 | muted: {
49 | DEFAULT: "hsl(var(--muted))",
50 | foreground: "hsl(var(--muted-foreground))",
51 | },
52 | accent: {
53 | DEFAULT: "hsl(var(--accent))",
54 | foreground: "hsl(var(--accent-foreground))",
55 | },
56 | popover: {
57 | DEFAULT: "hsl(var(--popover))",
58 | foreground: "hsl(var(--popover-foreground))",
59 | },
60 | card: {
61 | DEFAULT: "hsl(var(--card))",
62 | foreground: "hsl(var(--card-foreground))",
63 | },
64 | },
65 | borderRadius: {
66 | lg: `var(--radius)`,
67 | md: `calc(var(--radius) - 2px)`,
68 | sm: "calc(var(--radius) - 4px)",
69 | },
70 | fontFamily: {
71 | sans: ["Inter var", ...defaultTheme.fontFamily.sans],
72 | },
73 | keyframes: {
74 | "accordion-down": {
75 | from: { height: 0 },
76 | to: { height: "var(--radix-accordion-content-height)" },
77 | },
78 | "accordion-up": {
79 | from: { height: "var(--radix-accordion-content-height)" },
80 | to: { height: 0 },
81 | },
82 | },
83 | animation: {
84 | "accordion-down": "accordion-down 0.2s ease-out",
85 | "accordion-up": "accordion-up 0.2s ease-out",
86 | },
87 | },
88 | },
89 |
90 | plugins: [forms, require("tailwindcss-animate")],
91 | };
92 |
--------------------------------------------------------------------------------
/resources/views/vendor/laravelpwa/meta.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{--
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | --}}
29 |
30 |
31 |
32 |
33 |
34 | {{-- --}}
48 |
--------------------------------------------------------------------------------
/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_STORE', 'database'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Cache Stores
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the cache "stores" for your application as
26 | | well as their drivers. You may even define multiple stores for the
27 | | same cache driver to group types of items stored in your caches.
28 | |
29 | | Supported drivers: "array", "database", "file", "memcached",
30 | | "redis", "dynamodb", "octane", "null"
31 | |
32 | */
33 |
34 | 'stores' => [
35 |
36 | 'array' => [
37 | 'driver' => 'array',
38 | 'serialize' => false,
39 | ],
40 |
41 | 'database' => [
42 | 'driver' => 'database',
43 | 'connection' => env('DB_CACHE_CONNECTION'),
44 | 'table' => env('DB_CACHE_TABLE', 'cache'),
45 | 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
46 | 'lock_table' => env('DB_CACHE_LOCK_TABLE'),
47 | ],
48 |
49 | 'file' => [
50 | 'driver' => 'file',
51 | 'path' => storage_path('framework/cache/data'),
52 | 'lock_path' => storage_path('framework/cache/data'),
53 | ],
54 |
55 | 'memcached' => [
56 | 'driver' => 'memcached',
57 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
58 | 'sasl' => [
59 | env('MEMCACHED_USERNAME'),
60 | env('MEMCACHED_PASSWORD'),
61 | ],
62 | 'options' => [
63 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
64 | ],
65 | 'servers' => [
66 | [
67 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
68 | 'port' => env('MEMCACHED_PORT', 11211),
69 | 'weight' => 100,
70 | ],
71 | ],
72 | ],
73 |
74 | 'redis' => [
75 | 'driver' => 'redis',
76 | 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
77 | 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
78 | ],
79 |
80 | 'dynamodb' => [
81 | 'driver' => 'dynamodb',
82 | 'key' => env('AWS_ACCESS_KEY_ID'),
83 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
84 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
85 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
86 | 'endpoint' => env('DYNAMODB_ENDPOINT'),
87 | ],
88 |
89 | 'octane' => [
90 | 'driver' => 'octane',
91 | ],
92 |
93 | ],
94 |
95 | /*
96 | |--------------------------------------------------------------------------
97 | | Cache Key Prefix
98 | |--------------------------------------------------------------------------
99 | |
100 | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache
101 | | stores, there might be other applications using the same cache. For
102 | | that reason, you may prefix every cache key to avoid collisions.
103 | |
104 | */
105 |
106 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
107 |
108 | ];
109 |
--------------------------------------------------------------------------------