├── 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 | 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 |
    twMerge('space-y-8') }} 5 | > 6 | {{ $slot }} 7 |
    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 | 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 | 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 | 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 | 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 | Jata 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 | twMerge($sheet::new()(['side' => $side, 'variant' => 'sheet'])) }}> 10 | 12 | 13 | 14 | {{ $slot }} 15 | 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 | twMerge($dialog::new()(['side' => 'center', 'variant' => 'dialog'])) }}> 5 | {{ $slot }} 6 | 7 | 13 | 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 |
    15 | Register 16 |
    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 |
    20 | Login 21 | instead 22 |
    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 |
    12 |
    13 |
    14 |
    15 |
    16 |
    17 | 18 | {{--
    19 | 22 | 25 | 28 | 31 | 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 | 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 | 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 | 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 |
    35 | @csrf 36 | 39 |
    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 | 8 | 9 | 17 | 18 | 25 | 26 | 34 | 35 | 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 | --------------------------------------------------------------------------------