initialize project and update gitignore
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled

This commit is contained in:
PeterChrz 2026-03-19 09:35:42 -04:00
parent de55ae9a3b
commit 75561faf25
Signed by untrusted user who does not match committer: pch
GPG key ID: 8F0826ECF7302C63
163 changed files with 20905 additions and 0 deletions

18
.editorconfig Normal file
View file

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[compose.yaml]
indent_size = 4

65
.env.example Normal file
View file

@ -0,0 +1,65 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

11
.gitattributes vendored Normal file
View file

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
CHANGELOG.md export-ignore
README.md export-ignore
.github/workflows/browser-tests.yml export-ignore

46
.github/workflows/lint.yml vendored Normal file
View file

@ -0,0 +1,46 @@
name: linter
on:
push:
branches:
- develop
- main
pull_request:
branches:
- develop
- main
permissions:
contents: write
jobs:
quality:
runs-on: ubuntu-latest
environment: Testing
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- name: Add Flux Credentials Loaded From ENV
run: composer config http-basic.composer.fluxui.dev "${{ secrets.FLUX_USERNAME }}" "${{ secrets.FLUX_LICENSE_KEY }}"
- name: Install Dependencies
run: |
composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
npm install
- name: Run Pint
run: vendor/bin/pint
# - name: Commit Changes
# uses: stefanzweifel/git-auto-commit-action@v5
# with:
# commit_message: fix code style
# commit_options: '--no-verify'
# file_pattern: |
# **/*
# !.github/workflows/*

54
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,54 @@
name: tests
on:
push:
branches:
- develop
- main
pull_request:
branches:
- develop
- main
jobs:
ci:
runs-on: ubuntu-latest
environment: Testing
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.4
tools: composer:v2
coverage: xdebug
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install Node Dependencies
run: npm i
- name: Add Flux Credentials Loaded From ENV
run: composer config http-basic.composer.fluxui.dev "${{ secrets.FLUX_USERNAME }}" "${{ secrets.FLUX_LICENSE_KEY }}"
- name: Install Dependencies
run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Copy Environment File
run: cp .env.example .env
- name: Generate Application Key
run: php artisan key:generate
- name: Build Assets
run: npm run build
- name: Run Tests
run: ./vendor/bin/pest

24
.gitignore vendored
View file

@ -23,3 +23,27 @@ Homestead.json
/.vagrant /.vagrant
.phpunit.result.cache .phpunit.result.cache
# IDEs and local tools
.idea/
.vscode/
auth.json
# Laravel specific
.env
storage/*.key
/storage/framework/cache/data/*
/storage/framework/sessions/*
/storage/framework/views/*
/storage/logs/*
# Node / Build tools
/node_modules
/public/build
/public/hot
# Personal/Miscellaneous
*.sql
hours.txt
notes/
setup-nginx-openrc.php
setup-nginx.php

View file

@ -0,0 +1,39 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\CreatesNewUsers;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;
/**
* Validate and create a newly registered user.
*
* @param array<string, string> $input
*/
public function create(array $input): User
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique(User::class),
],
'password' => $this->passwordRules(),
])->validate();
return User::create([
'name' => $input['name'],
'email' => $input['email'],
'password' => $input['password'],
]);
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace App\Actions\Fortify;
use Illuminate\Validation\Rules\Password;
trait PasswordValidationRules
{
/**
* Get the validation rules used to validate passwords.
*
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
*/
protected function passwordRules(): array
{
return ['required', 'string', Password::default(), 'confirmed'];
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\ResetsUserPasswords;
class ResetUserPassword implements ResetsUserPasswords
{
use PasswordValidationRules;
/**
* Validate and reset the user's forgotten password.
*
* @param array<string, string> $input
*/
public function reset(User $user, array $input): void
{
Validator::make($input, [
'password' => $this->passwordRules(),
])->validate();
$user->forceFill([
'password' => $input['password'],
])->save();
}
}

52
app/Data/CategoryData.php Normal file
View file

@ -0,0 +1,52 @@
<?php
namespace App\Data;
use Livewire\Wireable;
use Spatie\LaravelData\Concerns\WireableData;
use Spatie\LaravelData\Data;
class CategoryData extends Data implements Wireable
{
use WireableData;
public function __construct(
public ?int $id,
public string $name,
public string $description,
public string $slug,
public string $color,
) {
}
public static function blank(): self
{
return CategoryData::from([
'name' => '',
'description' => '',
'slug' => '',
'color' => '',
]);
}
public static function rules(): array
{
return [
'name' => ['required', 'string', 'max:128', 'unique:categories,name'],
'description' => ['required', 'string', 'max:255'],
'slug' => ['required', 'string', 'max:32'],
'color' => ['required', 'string', 'max:32'],
];
}
public static function messages(): array
{
return [
'name.required' => __('The name field is required.'),
'name.unique' => __('The name has already been taken.'),
'description.required' => __('The description field is required.'),
'slug.required' => __('The slug field is required.'),
'color.required' => __('The color field is required.'),
];
}
}

69
app/Data/PostData.php Normal file
View file

@ -0,0 +1,69 @@
<?php
namespace App\Data;
use Illuminate\Support\Carbon;
use Livewire\Wireable;
use Spatie\LaravelData\Concerns\WireableData;
use Spatie\LaravelData\Data;
class PostData extends Data implements Wireable
{
use WireableData;
public function __construct(
public ?int $id,
public string $title,
public string $slug,
public string $content,
public string $excerpt,
public string $status,
public ?Carbon $published_at,
public ?int $author_id,
public ?int $category_id,
public ?array $tags,
) {
}
public static function blank(): self
{
return PostData::from([
'title' => '',
'slug' => '',
'content' => '',
'excerpt' => '',
'status' => 'draft',
'published_at' => null,
'author_id' => null,
'category_id' => null,
'tags' => [],
]);
}
public static function rules(): array
{
return [
'title' => ['required'],
'slug' => ['required'],
'content' => ['required'],
'excerpt' => ['required'],
'status' => ['required'],
'published_at' => ['nullable'],
'author_id' => ['required'],
'category_id' => ['required'],
];
}
public static function messages(): array
{
return [
'title.required' => __('The title field is required.'),
'slug.required' => __('The slug field is required.'),
'content.required' => __('The content field is required.'),
'excerpt.required' => __('The excerpt field is required.'),
'status.required' => __('The status field is required.'),
'author_id.required' => __('The author field is required.'),
'category_id.required' => __('The category field is required.'),
];
}
}

40
app/Data/TagData.php Normal file
View file

@ -0,0 +1,40 @@
<?php
namespace App\Data;
use Livewire\Wireable;
use Spatie\LaravelData\Concerns\WireableData;
use Spatie\LaravelData\Data;
class TagData extends Data implements Wireable
{
use WireableData;
public function __construct(
public ?int $id,
public string $name,
) {
}
public static function blank(): self
{
return TagData::from([
'name' => '',
]);
}
public static function rules(): array
{
return [
'name' => ['required', 'string', 'max:64', 'unique:tags,name'],
];
}
public static function messages(): array
{
return [
'name.required' => __('The name field is required.'),
'name.unique' => __('The name has already been taken.'),
];
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ContactController extends Controller
{
public function index()
{
return view('contactus.index');
}
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'surname' => 'required',
'email' => 'required|email',
]);
// @todo - actually send us an email.
return view('contactus.index', [
'message' => 'Thank you for contacting us, we will get back to you shortly.'
]);
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View file

@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers;
use App\Models\Post;
class PostController extends Controller
{
public function show(Post $post)
{
return view('posts.show', [
'post' => $post,
// @fixme @todo - come back and incorporate image post uploading in admin
'images' => [
'2' => 'https://images.unsplash.com/photo-1677442136019-21780ecad995?auto=format&fit=crop&q=80&w=800',
'3' => 'https://images.unsplash.com/photo-1667372393119-3d4c48d07fc9?auto=format&fit=crop&q=80&w=800',
'4' => 'https://images.unsplash.com/photo-1451187580459-43490279c0fa?auto=format&fit=crop&q=80&w=800',
],
]);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Livewire\Actions;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
class Logout
{
/**
* Log the current user out of the application.
*/
public function __invoke()
{
Auth::guard('web')->logout();
Session::invalidate();
Session::regenerateToken();
return redirect('/');
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace App\Livewire;
trait IncludesSorting
{
public ?string $sortBy = null;
public string $sortDirection = 'asc';
public function sort(string $column): void
{
if ($this->sortBy === $column) {
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
} else {
$this->sortBy = $column;
$this->sortDirection = 'desc';
}
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Livewire;
use Illuminate\Database\Eloquent\Builder;
trait WithDeleteFilter
{
public function handleDeletedFilter(Builder $query): Builder
{
if (!isset($this->filters['deleted'])) {
return $query;
}
return match ($this->filters['deleted']) {
'deleted' => $query->onlyTrashed(),
'all' => $query->withTrashed(),
default => $query,
};
}
}

27
app/Models/Category.php Normal file
View file

@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Category extends Model
{
/** @use HasFactory<\Database\Factories\CategoryFactory> */
use HasFactory;
use SoftDeletes;
protected $fillable = [
'name',
'description',
'slug',
'color',
];
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}

15
app/Models/Comment.php Normal file
View file

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Comment extends Model
{
/** @use HasFactory<\Database\Factories\CommentFactory> */
use HasFactory;
use SoftDeletes;
}

54
app/Models/Post.php Normal file
View file

@ -0,0 +1,54 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
/** @use HasFactory<\Database\Factories\PostFactory> */
use HasFactory;
use SoftDeletes;
protected $fillable = [
'title',
'slug',
'content',
'excerpt',
'status',
'published_at',
'author_id',
'category_id'
];
protected $casts = [
'published_at' => 'datetime',
];
protected function avgReadingTime(): Attribute
{
return new Attribute(
get: fn ($value) => ceil(str_word_count($this->content) / 100)
);
}
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'author_id');
}
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class);
}
}

10
app/Models/PostTag.php Normal file
View file

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PostTag extends Model
{
//
}

14
app/Models/Reaction.php Normal file
View file

@ -0,0 +1,14 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Reaction extends Model
{
/** @use HasFactory<\Database\Factories\ReactionFactory> */
use HasFactory;
use SoftDeletes;
}

19
app/Models/Tag.php Normal file
View file

@ -0,0 +1,19 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Tag extends Model
{
/** @use HasFactory<\Database\Factories\TagFactory> */
use HasFactory;
use SoftDeletes;
protected $fillable = [
'name',
];
}

64
app/Models/User.php Normal file
View file

@ -0,0 +1,64 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Str;
use Laravel\Fortify\TwoFactorAuthenticatable;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable, TwoFactorAuthenticatable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'two_factor_secret',
'two_factor_recovery_codes',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
/**
* Get the user's initials
*/
public function initials(): string
{
return Str::of($this->name)
->explode(' ')
->take(2)
->map(fn ($word) => Str::substr($word, 0, 1))
->implode('');
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Providers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
Model::preventLazyLoading(!app()->isProduction());
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace App\Providers;
use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Laravel\Fortify\Fortify;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
$this->configureActions();
$this->configureViews();
$this->configureRateLimiting();
}
/**
* Configure Fortify actions.
*/
private function configureActions(): void
{
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
Fortify::createUsersUsing(CreateNewUser::class);
}
/**
* Configure Fortify views.
*/
private function configureViews(): void
{
Fortify::loginView(fn () => view('livewire.auth.login'));
Fortify::verifyEmailView(fn () => view('livewire.auth.verify-email'));
Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge'));
Fortify::confirmPasswordView(fn () => view('livewire.auth.confirm-password'));
Fortify::registerView(fn () => view('livewire.auth.register'));
Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password'));
Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password'));
}
/**
* Configure rate limiting.
*/
private function configureRateLimiting(): void
{
RateLimiter::for('two-factor', function (Request $request) {
return Limit::perMinute(5)->by($request->session()->get('login.id'));
});
RateLimiter::for('login', function (Request $request) {
$throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());
return Limit::perMinute(5)->by($throttleKey);
});
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Livewire\Volt\Volt;
class VoltServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
//
}
/**
* Bootstrap services.
*/
public function boot(): void
{
Volt::mount([
config('livewire.view_path', resource_path('views/livewire')),
resource_path('views/pages'),
]);
}
}

18
artisan Executable file
View file

@ -0,0 +1,18 @@
#!/usr/bin/env php
<?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';
$status = $app->handleCommand(new ArgvInput);
exit($status);

18
bootstrap/app.php Normal file
View file

@ -0,0 +1,18 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
})
->withExceptions(function (Exceptions $exceptions): void {
//
})->create();

2
bootstrap/cache/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

7
bootstrap/providers.php Normal file
View file

@ -0,0 +1,7 @@
<?php
return [
App\Providers\AppServiceProvider::class,
App\Providers\FortifyServiceProvider::class,
App\Providers\VoltServiceProvider::class,
];

103
composer.json Normal file
View file

@ -0,0 +1,103 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/livewire-starter-kit",
"type": "project",
"description": "The official Laravel starter kit for Livewire.",
"keywords": [
"laravel",
"framework"
],
"license": "MIT",
"require": {
"php": "^8.2",
"laravel/fortify": "^1.30",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1",
"livewire/flux": "^2.1.1",
"livewire/flux-pro": "^2.6",
"livewire/livewire": "^4.1",
"livewire/volt": "^1.7.0",
"spatie/laravel-data": "^4.18"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"fruitcake/laravel-debugbar": "^4.0",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"pestphp/pest": "^4.1",
"pestphp/pest-plugin-laravel": "^4.0"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true,
"repositories": {
"flux-pro": {
"type": "composer",
"url": "https://composer.fluxui.dev"
}
}
}

10494
composer.lock generated Normal file

File diff suppressed because it is too large Load diff

126
config/app.php Normal file
View file

@ -0,0 +1,126 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application, which will be used when the
| framework needs to place the application's name in a notification or
| other UI elements where an application name needs to be displayed.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| the application so that it's available within Artisan commands.
|
*/
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. The timezone
| is set to "UTC" by default as it is suitable for most use cases.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by Laravel's translation / localization methods. This option can be
| set to any locale for which you plan to have translation strings.
|
*/
'locale' => env('APP_LOCALE', 'en'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is utilized by Laravel's encryption services and should be set
| to a random, 32 character string to ensure that all encrypted values
| are secure. You should do this prior to deploying the application.
|
*/
'cipher' => 'AES-256-CBC',
'key' => env('APP_KEY'),
'previous_keys' => [
...array_filter(
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
),
],
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],
];

115
config/auth.php Normal file
View file

@ -0,0 +1,115 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider.
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| If you have multiple user tables or models you may configure multiple
| providers to represent the model / table. These providers may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| These configuration options specify the behavior of Laravel's password
| reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the number of seconds before a password confirmation
| window expires and users are asked to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
];

117
config/cache.php Normal file
View file

@ -0,0 +1,117 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache store that will be used by the
| framework. This connection is utilized if another isn't explicitly
| specified when running a cache operation inside the application.
|
*/
'default' => env('CACHE_STORE', 'database'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "array", "database", "file", "memcached",
| "redis", "dynamodb", "octane",
| "failover", "null"
|
*/
'stores' => [
'array' => [
'driver' => 'array',
'serialize' => false,
],
'database' => [
'driver' => 'database',
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
'failover' => [
'driver' => 'failover',
'stores' => [
'database',
'array',
],
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
| stores, there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'),
];

183
config/database.php Normal file
View file

@ -0,0 +1,183 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Below are all of the database connections defined for your application.
| An example configuration is provided for each database system which
| is supported by Laravel. You're free to add / remove connections.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
'transaction_mode' => 'DEFERRED',
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'prefer',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run on the database.
|
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as Memcached. You may define your connection settings here.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
'persistent' => env('REDIS_PERSISTENT', false),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
],
];

80
config/filesystems.php Normal file
View file

@ -0,0 +1,80 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application for file storage.
|
*/
'default' => env('FILESYSTEM_DISK', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Below you may configure as many filesystem disks as necessary, and you
| may even configure multiple disks for the same driver. Examples for
| most supported storage drivers are configured here for reference.
|
| Supported drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app/private'),
'serve' => true,
'throw' => false,
'report' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
'report' => false,
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

159
config/fortify.php Normal file
View file

@ -0,0 +1,159 @@
<?php
use Laravel\Fortify\Features;
return [
/*
|--------------------------------------------------------------------------
| Fortify Guard
|--------------------------------------------------------------------------
|
| Here you may specify which authentication guard Fortify will use while
| authenticating users. This value should correspond with one of your
| guards that is already present in your "auth" configuration file.
|
*/
'guard' => 'web',
/*
|--------------------------------------------------------------------------
| Fortify Password Broker
|--------------------------------------------------------------------------
|
| Here you may specify which password broker Fortify can use when a user
| is resetting their password. This configured value should match one
| of your password brokers setup in your "auth" configuration file.
|
*/
'passwords' => 'users',
/*
|--------------------------------------------------------------------------
| Username / Email
|--------------------------------------------------------------------------
|
| This value defines which model attribute should be considered as your
| application's "username" field. Typically, this might be the email
| address of the users but you are free to change this value here.
|
| Out of the box, Fortify expects forgot password and reset password
| requests to have a field named 'email'. If the application uses
| another name for the field you may define it below as needed.
|
*/
'username' => 'email',
'email' => 'email',
/*
|--------------------------------------------------------------------------
| Lowercase Usernames
|--------------------------------------------------------------------------
|
| This value defines whether usernames should be lowercased before saving
| them in the database, as some database system string fields are case
| sensitive. You may disable this for your application if necessary.
|
*/
'lowercase_usernames' => true,
/*
|--------------------------------------------------------------------------
| Home Path
|--------------------------------------------------------------------------
|
| Here you may configure the path where users will get redirected during
| authentication or password reset when the operations are successful
| and the user is authenticated. You are free to change this value.
|
*/
'home' => '/dashboard',
/*
|--------------------------------------------------------------------------
| Fortify Routes Prefix / Subdomain
|--------------------------------------------------------------------------
|
| Here you may specify which prefix Fortify will assign to all the routes
| that it registers with the application. If necessary, you may change
| subdomain under which all of the Fortify routes will be available.
|
*/
'prefix' => '',
'domain' => null,
/*
|--------------------------------------------------------------------------
| Fortify Routes Middleware
|--------------------------------------------------------------------------
|
| Here you may specify which middleware Fortify will assign to the routes
| that it registers with the application. If necessary, you may change
| these middleware but typically this provided default is preferred.
|
*/
'middleware' => ['web'],
/*
|--------------------------------------------------------------------------
| Rate Limiting
|--------------------------------------------------------------------------
|
| By default, Fortify will throttle logins to five requests per minute for
| every email and IP address combination. However, if you would like to
| specify a custom rate limiter to call then you may specify it here.
|
*/
'limiters' => [
'login' => 'login',
'two-factor' => 'two-factor',
],
/*
|--------------------------------------------------------------------------
| Register View Routes
|--------------------------------------------------------------------------
|
| Here you may specify if the routes returning views should be disabled as
| you may not need them when building your own application. This may be
| especially true if you're writing a custom single-page application.
|
*/
'views' => true,
/*
|--------------------------------------------------------------------------
| Features
|--------------------------------------------------------------------------
|
| Some of the Fortify features are optional. You may disable the features
| by removing them from this array. You're free to only remove some of
| these features or you can even remove all of these if you need to.
|
*/
'features' => [
Features::registration(),
Features::resetPasswords(),
Features::emailVerification(),
// Features::updateProfileInformation(),
// Features::updatePasswords(),
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => true,
// 'window' => 0,
]),
],
];

282
config/livewire.php Normal file
View file

@ -0,0 +1,282 @@
<?php
return [
/*
|---------------------------------------------------------------------------
| Component Locations
|---------------------------------------------------------------------------
|
| This value sets the root directories that'll be used to resolve view-based
| components like single and multi-file components. The make command will
| use the first directory in this array to add new component files to.
|
*/
'component_locations' => [
resource_path('views/components'),
resource_path('views/livewire'),
],
/*
|---------------------------------------------------------------------------
| Component Namespaces
|---------------------------------------------------------------------------
|
| This value sets default namespaces that will be used to resolve view-based
| components like single-file and multi-file components. These folders'll
| also be referenced when creating new components via the make command.
|
*/
'component_namespaces' => [
'layouts' => resource_path('views/layouts'),
'pages' => resource_path('views/pages'),
],
/*
|---------------------------------------------------------------------------
| Page Layout
|---------------------------------------------------------------------------
| The view that will be used as the layout when rendering a single component as
| an entire page via `Route::livewire('/post/create', 'pages::create-post')`.
| In this case, the content of pages::create-post will render into $slot.
|
*/
'component_layout' => 'layouts::app',
/*
|---------------------------------------------------------------------------
| Lazy Loading Placeholder
|---------------------------------------------------------------------------
| Livewire allows you to lazy load components that would otherwise slow down
| the initial page load. Every component can have a custom placeholder or
| you can define the default placeholder view for all components below.
|
*/
'component_placeholder' => null, // Example: 'placeholders::skeleton'
/*
|---------------------------------------------------------------------------
| Make Command
|---------------------------------------------------------------------------
| This value determines the default configuration for the artisan make command
| You can configure the component type (sfc, mfc, class) and whether to use
| the high-voltage () emoji as a prefix in the sfc|mfc component names.
|
*/
'make_command' => [
'type' => 'sfc', // Options: 'sfc', 'mfc', 'class'
'emoji' => true, // Options: true, false
'with' => [
'js' => false,
'css' => false,
'test' => false,
],
],
/*
|---------------------------------------------------------------------------
| Class Namespace
|---------------------------------------------------------------------------
|
| This value sets the root class namespace for Livewire component classes in
| your application. This value will change where component auto-discovery
| finds components. It's also referenced by the file creation commands.
|
*/
'class_namespace' => 'App\\Livewire',
/*
|---------------------------------------------------------------------------
| Class Path
|---------------------------------------------------------------------------
|
| This value is used to specify the path where Livewire component class files
| are created when running creation commands like `artisan make:livewire`.
| This path is customizable to match your projects directory structure.
|
*/
'class_path' => app_path('Livewire'),
/*
|---------------------------------------------------------------------------
| View Path
|---------------------------------------------------------------------------
|
| This value is used to specify where Livewire component Blade templates are
| stored when running file creation commands like `artisan make:livewire`.
| It is also used if you choose to omit a component's render() method.
|
*/
'view_path' => resource_path('views/livewire'),
/*
|---------------------------------------------------------------------------
| Temporary File Uploads
|---------------------------------------------------------------------------
|
| Livewire handles file uploads by storing uploads in a temporary directory
| before the file is stored permanently. All file uploads are directed to
| a global endpoint for temporary storage. You may configure this below:
|
*/
'temporary_file_upload' => [
'disk' => env('LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK'), // Example: 'local', 's3' | Default: 'default'
'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp'
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
'mov', 'avi', 'wmv', 'mp3', 'm4a',
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
],
'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated...
'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs...
],
/*
|---------------------------------------------------------------------------
| Render On Redirect
|---------------------------------------------------------------------------
|
| This value determines if Livewire will run a component's `render()` method
| after a redirect has been triggered using something like `redirect(...)`
| Setting this to true will render the view once more before redirecting
|
*/
'render_on_redirect' => false,
/*
|---------------------------------------------------------------------------
| Eloquent Model Binding
|---------------------------------------------------------------------------
|
| Previous versions of Livewire supported binding directly to eloquent model
| properties using wire:model by default. However, this behavior has been
| deemed too "magical" and has therefore been put under a feature flag.
|
*/
'legacy_model_binding' => false,
/*
|---------------------------------------------------------------------------
| Auto-inject Frontend Assets
|---------------------------------------------------------------------------
|
| By default, Livewire automatically injects its JavaScript and CSS into the
| <head> and <body> of pages containing Livewire components. By disabling
| this behavior, you need to use @livewireStyles and @livewireScripts.
|
*/
'inject_assets' => true,
/*
|---------------------------------------------------------------------------
| Navigate (SPA mode)
|---------------------------------------------------------------------------
|
| By adding `wire:navigate` to links in your Livewire application, Livewire
| will prevent the default link handling and instead request those pages
| via AJAX, creating an SPA-like effect. Configure this behavior here.
|
*/
'navigate' => [
'show_progress_bar' => true,
'progress_bar_color' => '#2299dd',
],
/*
|---------------------------------------------------------------------------
| HTML Morph Markers
|---------------------------------------------------------------------------
|
| Livewire intelligently "morphs" existing HTML into the newly rendered HTML
| after each update. To make this process more reliable, Livewire injects
| "markers" into the rendered Blade surrounding @if, @class & @foreach.
|
*/
'inject_morph_markers' => true,
/*
|---------------------------------------------------------------------------
| Smart Wire Keys
|---------------------------------------------------------------------------
|
| Livewire uses loops and keys used within loops to generate smart keys that
| are applied to nested components that don't have them. This makes using
| nested components more reliable by ensuring that they all have keys.
|
*/
'smart_wire_keys' => true,
/*
|---------------------------------------------------------------------------
| Pagination Theme
|---------------------------------------------------------------------------
|
| When enabling Livewire's pagination feature by using the `WithPagination`
| trait, Livewire will use Tailwind templates to render pagination views
| on the page. If you want Bootstrap CSS, you can specify: "bootstrap"
|
*/
'pagination_theme' => 'tailwind',
/*
|---------------------------------------------------------------------------
| Release Token
|---------------------------------------------------------------------------
|
| This token is stored client-side and sent along with each request to check
| a users session to see if a new release has invalidated it. If there is
| a mismatch it will throw an error and prompt for a browser refresh.
|
*/
'release_token' => 'a',
/*
|---------------------------------------------------------------------------
| CSP Safe
|---------------------------------------------------------------------------
|
| This config is used to determine if Livewire will use the CSP-safe version
| of Alpine in its bundle. This is useful for applications that are using
| strict Content Security Policy (CSP) to protect against XSS attacks.
|
*/
'csp_safe' => false,
/*
|---------------------------------------------------------------------------
| Payload Guards
|---------------------------------------------------------------------------
|
| These settings protect against malicious or oversized payloads that could
| cause denial of service. The default values should feel reasonable for
| most web applications. Each can be set to null to disable the limit.
|
*/
'payload' => [
'max_size' => 1024 * 1024, // 1MB - maximum request payload size in bytes
'max_nesting_depth' => 10, // Maximum depth of dot-notation property paths
'max_calls' => 50, // Maximum method calls per request
'max_components' => 20, // Maximum components per batch request
],
];

132
config/logging.php Normal file
View file

@ -0,0 +1,132 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that is utilized to write
| messages to your logs. The value provided here should match one of
| the channels present in the list of "channels" configured below.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
*/
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Laravel
| utilizes the Monolog PHP logging library, which includes a variety
| of powerful log handlers and formatters that you're free to use.
|
| Available drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'handler_with' => [
'stream' => 'php://stderr',
],
'formatter' => env('LOG_STDERR_FORMATTER'),
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];

118
config/mail.php Normal file
View file

@ -0,0 +1,118 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send all email
| messages unless another mailer is explicitly specified when sending
| the message. All additional mailers can be configured within the
| "mailers" array. Examples of each type of mailer are provided.
|
*/
'default' => env('MAIL_MAILER', 'log'),
/*
|--------------------------------------------------------------------------
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers that can be used
| when delivering an email. You may specify which one you're using for
| your mailers below. You may also add additional mailers if needed.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "resend", "log", "array",
| "failover", "roundrobin"
|
*/
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
],
'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],
'resend' => [
'transport' => 'resend',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
'retry_after' => 60,
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
'retry_after' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all emails sent by your application to be sent from
| the same address. Here you may specify a name and address that is
| used globally for all emails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
];

129
config/queue.php Normal file
View file

@ -0,0 +1,129 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each backend using identical
| syntax for each. The default queue connection is defined below.
|
*/
'default' => env('QUEUE_CONNECTION', 'database'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection options for every queue backend
| used by your application. An example configuration is provided for
| each backend supported by Laravel. You're also free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
| "deferred", "background", "failover", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'block_for' => 0,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
'deferred' => [
'driver' => 'deferred',
],
'background' => [
'driver' => 'background',
],
'failover' => [
'driver' => 'failover',
'connections' => [
'database',
'deferred',
],
],
],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'job_batches',
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control how and where failed jobs are stored. Laravel ships with
| support for storing failed jobs in a simple file or in a database.
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs',
],
];

38
config/services.php Normal file
View file

@ -0,0 +1,38 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],
'resend' => [
'key' => env('RESEND_API_KEY'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
];

217
config/session.php Normal file
View file

@ -0,0 +1,217 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option determines the default session driver that is utilized for
| incoming requests. Laravel supports a variety of storage options to
| persist session data. Database storage is a great default choice.
|
| Supported: "file", "cookie", "database", "memcached",
| "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to expire immediately when the browser is closed then you may
| indicate that via the expire_on_close configuration option.
|
*/
'lifetime' => (int) env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it's stored. All encryption is performed
| automatically by Laravel and you may use the session like normal.
|
*/
'encrypt' => env('SESSION_ENCRYPT', false),
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When utilizing the "file" session driver, the session files are placed
| on disk. The default storage location is defined here; however, you
| are free to provide another location where they should be stored.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table to
| be used to store sessions. Of course, a sensible default is defined
| for you; however, you're welcome to change this to another table.
|
*/
'table' => env('SESSION_TABLE', 'sessions'),
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| When using one of the framework's cache driven session backends, you may
| define the cache store which should be used to store the session data
| between requests. This must match one of your defined cache stores.
|
| Affects: "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the session cookie that is created by
| the framework. Typically, you should not need to change this value
| since doing so does not grant a meaningful security improvement.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug((string) env('APP_NAME', 'laravel')).'-session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application, but you're free to change this when necessary.
|
*/
'path' => env('SESSION_PATH', '/'),
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| This value determines the domain and subdomains the session cookie is
| available to. By default, the cookie will be available to the root
| domain and all subdomains. Typically, this shouldn't be changed.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. It's unlikely you should disable this option.
|
*/
'http_only' => env('SESSION_HTTP_ONLY', true),
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" to permit secure cross-site requests.
|
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
];

1
database/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.sqlite*

View file

@ -0,0 +1,26 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Category>
*/
class CategoryFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => $this->faker->name(),
'slug' => Str::slug($this->faker->word()),
'description' => $this->faker->text(),
];
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Comment>
*/
class CommentFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
*/
class PostFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Reaction>
*/
class ReactionFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Tag>
*/
class TagFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= 'password',
'remember_token' => Str::random(10),
'two_factor_secret' => Str::random(10),
'two_factor_recovery_codes' => Str::random(10),
'two_factor_confirmed_at' => now(),
];
}
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
/**
* Indicate that the model does not have two-factor authentication configured.
*/
public function withoutTwoFactor(): static
{
return $this->state(fn (array $attributes) => [
'two_factor_secret' => null,
'two_factor_recovery_codes' => null,
'two_factor_confirmed_at' => null,
]);
}
}

View file

@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View file

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};

View file

@ -0,0 +1,57 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jobs');
Schema::dropIfExists('job_batches');
Schema::dropIfExists('failed_jobs');
}
};

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->text('two_factor_secret')->after('password')->nullable();
$table->text('two_factor_recovery_codes')->after('two_factor_secret')->nullable();
$table->timestamp('two_factor_confirmed_at')->after('two_factor_recovery_codes')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn([
'two_factor_secret',
'two_factor_recovery_codes',
'two_factor_confirmed_at',
]);
});
}
};

View file

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title', 128);
$table->string('slug', 64);
$table->longText('content');
$table->text('excerpt');
$table->string('status', 32)->comment('draft, published, archived');
$table->dateTime('published_at')->nullable();
$table->foreignId('author_id')->nullable();
$table->foreignId('category_id')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('posts');
}
};

View file

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name', 128);
$table->string('slug', 32)->comment('Usually an uppercase slug');
$table->string('color', 32)->nullable();
$table->text('description')->nullable();
$table->unsignedBigInteger('parent_id')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('categories');
}
};

View file

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name', 64);
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tags');
}
};

View file

@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('post_tag', function (Blueprint $table) {
$table->foreignId('post_id');
$table->foreignId('tag_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('post_tag');
}
};

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->text('content');
$table->foreignId('post_id');
$table->foreignId('parent_comment_id');
$table->string('status', 32)->comment('approved, pending');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('comments');
}
};

View file

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('reactions', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable();
$table->morphs('reactable');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('reactions');
}
};

View file

@ -0,0 +1,33 @@
<?php
namespace Database\Seeders;
use App\Models\Category;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
class CategorySeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$categories = [
['name' => 'Cloud Computing', 'color' => 'orange'],
['name' => 'HomeLab', 'color' => 'indigo'],
['name' => 'Networking', 'color' => 'lime'],
['name' => 'Virtualization', 'color' => 'sky'],
];
foreach ($categories as $category) {
Category::updateOrCreate(
['slug' => Str::kebab($category['name'])],
[
'name' => $category['name'],
'color' => $category['color'],
]
);
}
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class CommentSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Database\Seeders;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
$this->call([
CategorySeeder::class,
TagSeeder::class,
UserSeeder::class,
PostSeeder::class,
]);
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Database\Seeders;
use App\Models\Category;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
class PostSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$author = User::where('email', 'contact@techblog.com')->first();
if (!$author) {
return;
}
$posts = [
[
'title' => 'The Future of Cloud Computing in 2026',
'category' => 'Cloud Computing',
'excerpt' => 'Exploring the impact of edge computing, serverless architectures, and AI integration on the cloud landscape.',
'content' => "As we move into 2026, cloud computing continues to evolve at a breakneck pace. The boundaries between edge and cloud are blurring, with more processing power moving closer to the data source. Serverless architectures have matured, allowing developers to focus entirely on code rather than infrastructure.
AI integration is no longer a luxury but a core component of cloud services. From automated resource scaling to intelligent threat detection, AI is making cloud environments more efficient and secure. In this article, we'll dive deep into these trends and what they mean for the next generation of software development.",
],
[
'title' => 'Securing Your Home Network: A Comprehensive Guide',
'category' => 'Networking',
'excerpt' => 'Learn how to segment your network using VLANs and firewalls to protect your devices from external threats.',
'content' => "In an era where every household has dozens of connected devices, network security has never been more important. This guide covers the essentials of securing your home network, starting with the basics of firewall configuration and moving into more advanced topics like network segmentation.
We will explore how to use VLANs to isolate IoT devices from your primary network, preventing a compromised smart bulb from becoming a gateway to your personal computer. Additionally, we'll discuss the benefits of running a local DNS sinkhole like Pi-hole or AdGuard Home to block ads and trackers at the network level.",
],
[
'title' => 'Getting Started with Proxmox VE',
'category' => 'Virtualization',
'excerpt' => 'A step-by-step guide to installing and configuring Proxmox VE for your home lab environment.',
'content' => "Proxmox Virtual Environment is a complete open-source platform for enterprise virtualization. It tightly integrates KVM hypervisor and Linux Containers (LXC), software-defined storage, and networking functionality on a single platform.
For home lab enthusiasts, Proxmox offers a powerful yet accessible way to manage multiple virtual machines and containers. This post will walk you through the initial installation process, from creating a bootable USB to configuring your first bridge interface. We'll also cover some best practices for storage management and ZFS configuration to ensure your data remains safe and performant.",
],
];
foreach ($posts as $postData) {
$category = Category::where('name', $postData['category'])->first();
if ($category) {
Post::updateOrCreate(
['slug' => Str::slug($postData['title'])],
[
'title' => $postData['title'],
'content' => $postData['content'],
'excerpt' => $postData['excerpt'],
'status' => 'published',
'published_at' => now(),
'author_id' => $author->id,
'category_id' => $category->id,
]
);
}
}
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class ReactionSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Database\Seeders;
use App\Models\Tag;
use Illuminate\Database\Seeder;
class TagSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$tags = ['AWS', 'Kubernetes Clusters', 'NAS'];
foreach ($tags as $tag) {
Tag::factory()->create([
'name' => $tag,
]);
}
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Database\Seeders;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Database\Seeder;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
User::firstOrCreate(
['email' => 'contact@techblog.com'],
[
'name' => 'Techblog Superuser',
'password' => bcrypt('password'),
]
);
}
}

2450
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

23
package.json Normal file
View file

@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.11",
"autoprefixer": "^10.4.20",
"axios": "^1.7.4",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0",
"tailwindcss": "^4.0.7",
"vite": "^7.0.4"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "4.9.5",
"@tailwindcss/oxide-linux-x64-gnu": "^4.0.1",
"lightningcss-linux-x64-gnu": "^1.29.1"
}
}

35
phpunit.xml Normal file
View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="BROADCAST_CONNECTION" value="null"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="TELESCOPE_ENABLED" value="false"/>
<env name="NIGHTWATCH_ENABLED" value="false"/>
</php>
</phpunit>

25
public/.htaccess Normal file
View file

@ -0,0 +1,25 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Handle X-XSRF-Token Header
RewriteCond %{HTTP:x-xsrf-token} .
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

3
public/favicon.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="166" height="166" viewBox="0 0 166 166" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M162.041 38.7592C162.099 38.9767 162.129 39.201 162.13 39.4264V74.4524C162.13 74.9019 162.011 75.3435 161.786 75.7325C161.561 76.1216 161.237 76.4442 160.847 76.6678L131.462 93.5935V127.141C131.462 128.054 130.977 128.897 130.186 129.357L68.8474 164.683C68.707 164.763 68.5538 164.814 68.4007 164.868C68.3432 164.887 68.289 164.922 68.2284 164.938C67.7996 165.051 67.3489 165.051 66.9201 164.938C66.8499 164.919 66.7861 164.881 66.7191 164.855C66.5787 164.804 66.4319 164.76 66.2979 164.683L4.97219 129.357C4.58261 129.133 4.2589 128.81 4.0337 128.421C3.8085 128.032 3.68976 127.591 3.68945 127.141L3.68945 22.0634C3.68945 21.8336 3.72136 21.6101 3.7788 21.393C3.79794 21.3196 3.84262 21.2526 3.86814 21.1791C3.91601 21.0451 3.96068 20.9078 4.03088 20.7833C4.07874 20.7003 4.14894 20.6333 4.20638 20.5566C4.27977 20.4545 4.34678 20.3491 4.43293 20.2598C4.50632 20.1863 4.60205 20.1321 4.68501 20.0682C4.77755 19.9916 4.86051 19.9086 4.96581 19.848L35.6334 2.18492C36.0217 1.96139 36.4618 1.84375 36.9098 1.84375C37.3578 1.84375 37.7979 1.96139 38.1862 2.18492L68.8506 19.848H68.857C68.9591 19.9118 69.0452 19.9916 69.1378 20.065C69.2207 20.1289 69.3133 20.1863 69.3867 20.2566C69.476 20.3491 69.5398 20.4545 69.6164 20.5566C69.6707 20.6333 69.7441 20.7003 69.7887 20.7833C69.8621 20.911 69.9036 21.0451 69.9546 21.1791C69.9802 21.2526 70.0248 21.3196 70.044 21.3962C70.1027 21.6138 70.1328 21.8381 70.1333 22.0634V87.6941L95.686 72.9743V39.4232C95.686 39.1997 95.7179 38.9731 95.7753 38.7592C95.7977 38.6826 95.8391 38.6155 95.8647 38.5421C95.9157 38.408 95.9604 38.2708 96.0306 38.1463C96.0785 38.0633 96.1487 37.9962 96.2029 37.9196C96.2795 37.8175 96.3433 37.7121 96.4326 37.6227C96.506 37.5493 96.5986 37.495 96.6815 37.4312C96.7773 37.3546 96.8602 37.2716 96.9623 37.2109L127.633 19.5479C128.021 19.324 128.461 19.2062 128.91 19.2062C129.358 19.2062 129.798 19.324 130.186 19.5479L160.85 37.2109C160.959 37.2748 161.042 37.3546 161.137 37.428C161.217 37.4918 161.31 37.5493 161.383 37.6195C161.473 37.7121 161.536 37.8175 161.613 37.9196C161.67 37.9962 161.741 38.0633 161.785 38.1463C161.859 38.2708 161.9 38.408 161.951 38.5421C161.98 38.6155 162.021 38.6826 162.041 38.7592ZM157.018 72.9743V43.8477L146.287 50.028L131.462 58.5675V87.6941L157.021 72.9743H157.018ZM126.354 125.663V96.5176L111.771 104.85L70.1301 128.626V158.046L126.354 125.663ZM8.80126 26.4848V125.663L65.0183 158.043V128.629L35.6494 112L35.6398 111.994L35.6271 111.988C35.5281 111.93 35.4452 111.847 35.3526 111.777C35.2729 111.713 35.1803 111.662 35.1101 111.592L35.1038 111.582C35.0208 111.502 34.9634 111.403 34.8932 111.314C34.8293 111.228 34.7528 111.154 34.7017 111.065L34.6985 111.055C34.6411 110.96 34.606 110.845 34.5645 110.736C34.523 110.64 34.4688 110.551 34.4432 110.449C34.4113 110.328 34.4049 110.197 34.3922 110.072C34.3794 109.976 34.3539 109.881 34.3539 109.785V109.778V41.2045L19.5322 32.6619L8.80126 26.4848ZM36.913 7.35007L11.3635 22.0634L36.9066 36.7768L62.4529 22.0602L36.9066 7.35007H36.913ZM50.1999 99.1736L65.0215 90.6374V26.4848L54.2906 32.6651L39.4657 41.2045V105.357L50.1999 99.1736ZM128.91 24.713L103.363 39.4264L128.91 54.1397L154.453 39.4232L128.91 24.713ZM126.354 58.5675L111.529 50.028L100.798 43.8477V72.9743L115.619 81.5106L126.354 87.6941V58.5675ZM67.5711 124.205L105.042 102.803L123.772 92.109L98.2451 77.4053L68.8538 94.3341L42.0663 109.762L67.5711 124.205Z" fill="#FF2D20"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

20
public/index.php Normal file
View file

@ -0,0 +1,20 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';
// Bootstrap Laravel and handle the request...
/** @var Application $app */
$app = require_once __DIR__.'/../bootstrap/app.php';
$app->handleRequest(Request::capture());

2
public/robots.txt Normal file
View file

@ -0,0 +1,2 @@
User-agent: *
Disallow:

66
resources/css/app.css Normal file
View file

@ -0,0 +1,66 @@
@import 'tailwindcss';
@import '../../vendor/livewire/flux/dist/flux.css';
@source '../views';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../vendor/livewire/flux-pro/stubs/**/*.blade.php';
@source '../../vendor/livewire/flux/stubs/**/*.blade.php';
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--color-zinc-50: #fafafa;
--color-zinc-100: #f5f5f5;
--color-zinc-200: #e5e5e5;
--color-zinc-300: #d4d4d4;
--color-zinc-400: #a3a3a3;
--color-zinc-500: #737373;
--color-zinc-600: #525252;
--color-zinc-700: #404040;
--color-zinc-800: #262626;
--color-zinc-900: #171717;
--color-zinc-950: #0a0a0a;
--color-accent: var(--color-neutral-800);
--color-accent-content: var(--color-neutral-800);
--color-accent-foreground: var(--color-white);
}
@layer theme {
.dark {
--color-accent: var(--color-white);
--color-accent-content: var(--color-white);
--color-accent-foreground: var(--color-neutral-800);
}
}
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
[data-flux-field]:not(ui-radio, ui-checkbox) {
@apply grid gap-2;
}
[data-flux-label] {
@apply !mb-0 !leading-tight;
}
input:focus[data-flux-control],
textarea:focus[data-flux-control],
select:focus[data-flux-control] {
@apply outline-hidden ring-2 ring-accent ring-offset-2 ring-offset-accent-foreground;
}
/* \[:where(&)\]:size-4 {
@apply size-4;
} */

0
resources/js/app.js Normal file
View file

View file

@ -0,0 +1,14 @@
@props([
'on',
])
<div
x-data="{ shown: false, timeout: null }"
x-init="@this.on('{{ $on }}', () => { clearTimeout(timeout); shown = true; timeout = setTimeout(() => { shown = false }, 2000); })"
x-show.transition.out.opacity.duration.1500ms="shown"
x-transition:leave.opacity.duration.1500ms
style="display: none"
{{ $attributes->merge(['class' => 'text-sm']) }}
>
{{ $slot->isEmpty() ? __('Saved.') : $slot }}
</div>

View file

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 42" {{ $attributes }}>
<path
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
d="M17.2 5.633 8.6.855 0 5.633v26.51l16.2 9 16.2-9v-8.442l7.6-4.223V9.856l-8.6-4.777-8.6 4.777V18.3l-5.6 3.111V5.633ZM38 18.301l-5.6 3.11v-6.157l5.6-3.11V18.3Zm-1.06-7.856-5.54 3.078-5.54-3.079 5.54-3.078 5.54 3.079ZM24.8 18.3v-6.157l5.6 3.111v6.158L24.8 18.3Zm-1 1.732 5.54 3.078-13.14 7.302-5.54-3.078 13.14-7.3v-.002Zm-16.2 7.89 7.6 4.222V38.3L2 30.966V7.92l5.6 3.111v16.892ZM8.6 9.3 3.06 6.222 8.6 3.143l5.54 3.08L8.6 9.3Zm21.8 15.51-13.2 7.334V38.3l13.2-7.334v-6.156ZM9.6 11.034l5.6-3.11v14.6l-5.6 3.11v-14.6Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 714 B

View file

@ -0,0 +1,6 @@
<div class="flex aspect-square size-8 items-center justify-center rounded-md bg-accent-content text-accent-foreground">
<x-app-logo-icon class="size-5 fill-current text-white dark:text-black" />
</div>
<div class="ms-1 grid flex-1 text-start text-sm">
<span class="mb-0.5 truncate leading-tight font-semibold">Laravel Starter Kit</span>
</div>

View file

@ -0,0 +1,9 @@
@props([
'title',
'description',
])
<div class="flex w-full flex-col text-center">
<flux:heading size="xl">{{ $title }}</flux:heading>
<flux:subheading>{{ $description }}</flux:subheading>
</div>

View file

@ -0,0 +1,9 @@
@props([
'status',
])
@if ($status)
<div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}>
{{ $status }}
</div>
@endif

View file

@ -0,0 +1,2 @@
@props(['category'])
<flux:badge size="sm" :color="$category->color">{{ $category->name }}</flux:badge>

View file

@ -0,0 +1,25 @@
@props(['filters'])
@if (!empty(array_filter($filters)))
<div class="flex space-x-2 mb-2">
<flux:badge color="blue" size="sm">@lang('Applied Filters')</flux:badge>
@foreach ($filters as $key => $value)
@if ($value)
<flux:badge size="sm">{{ ucfirst($key) }}: {{ $value }}</flux:badge>
@endif
@endforeach
</div>
@endif
<div class="flex items-center justify-between">
<flux:input icon="magnifying-glass" wire:model.live.debounce.300ms="filters.search" class="max-w-64" placeholder="{{ __('Search...') }}" autocomplete="off">
<x-slot name="iconTrailing">
<flux:button wire:click="$set('filters.search', null)" icon="x-mark" variant="subtle" size="sm" class="-mr-1"/>
</x-slot>
</flux:input>
<flux:dropdown position="bottom" align="end">
<flux:button variant="ghost" size="sm" icon-variant="outline" icon="funnel"/>
<flux:menu class="space-y-2">
{{ $slot }}
</flux:menu>
</flux:dropdown>
</div>

View file

@ -0,0 +1,138 @@
@props([
'digits' => 6,
'name' => 'code',
])
<div
@focus-2fa-auth-code.window="$refs.input1?.focus()"
@clear-2fa-auth-code.window="clearAll()"
class="relative"
x-data="{
totalDigits: @js($digits),
digitIndices: @js(range(1, $digits)),
init() {
$nextTick(() => {
this.$refs.input1?.focus();
});
},
getInput(index) {
return this.$refs['input' + index];
},
setValue(index, value) {
this.getInput(index).value = value;
},
getCode() {
return this.digitIndices
.map(i => this.getInput(i).value)
.join('');
},
updateHiddenField() {
this.$refs.code.value = this.getCode();
this.$refs.code.dispatchEvent(new Event('input', { bubbles: true }));
this.$refs.code.dispatchEvent(new Event('change', { bubbles: true }));
},
handleNumberKey(index, key) {
this.setValue(index, key);
if (index < this.totalDigits) {
this.getInput(index + 1).focus();
}
$nextTick(() => {
this.updateHiddenField();
});
},
handleBackspace(index) {
const currentInput = this.getInput(index);
if (currentInput.value !== '') {
currentInput.value = '';
this.updateHiddenField();
return;
}
if (index <= 1) {
return;
}
const previousInput = this.getInput(index - 1);
previousInput.value = '';
previousInput.focus();
this.updateHiddenField();
},
handleKeyDown(index, event) {
const key = event.key;
if (/^[0-9]$/.test(key)) {
event.preventDefault();
this.handleNumberKey(index, key);
return;
}
if (key === 'Backspace') {
event.preventDefault();
this.handleBackspace(index);
return;
}
},
handlePaste(event) {
event.preventDefault();
const pastedText = (event.clipboardData || window.clipboardData).getData('text');
const numericOnly = pastedText.replace(/[^0-9]/g, '');
const digitsToFill = Math.min(numericOnly.length, this.totalDigits);
this.digitIndices
.slice(0, digitsToFill)
.forEach(index => {
this.setValue(index, numericOnly[index - 1]);
});
if (numericOnly.length >= this.totalDigits) {
this.updateHiddenField();
}
},
clearAll() {
this.digitIndices.forEach(index => {
this.setValue(index, '');
});
this.$refs.code.value = '';
this.$refs.input1?.focus();
}
}"
>
<div class="flex items-center">
@for ($x = 1; $x <= $digits; $x++)
<input
x-ref="input{{ $x }}"
type="text"
inputmode="numeric"
pattern="[0-9]"
maxlength="1"
autocomplete="off"
@paste="handlePaste"
@keydown="handleKeyDown({{ $x }}, $event)"
@focus="$el.select()"
@input="$el.value = $el.value.replace(/[^0-9]/g, '').slice(0, 1)"
@class([
'flex size-10 items-center justify-center border border-zinc-300 bg-accent-foreground text-center text-sm font-medium text-accent-content transition-colors focus:border-accent focus:border-2 focus:outline-none focus:relative focus:z-10 dark:border-zinc-700 dark:focus:border-accent',
'rounded-l-md' => $x === 1,
'rounded-r-md' => $x === $digits,
'-ml-px' => $x > 1,
])
/>
@endfor
</div>
<input
{{ $attributes->except(['digits']) }}
type="hidden"
x-ref="code"
name="{{ $name }}"
minlength="{{ $digits }}"
maxlength="{{ $digits }}"
/>
</div>

View file

@ -0,0 +1,12 @@
@props([
'id' => uniqid(),
])
<svg {{ $attributes }} fill="none">
<defs>
<pattern id="pattern-{{ $id }}" x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
<path d="M-1 5L5 -1M3 9L8.5 3.5" stroke-width="0.5"></path>
</pattern>
</defs>
<rect stroke="none" fill="url(#pattern-{{ $id }})" width="100%" height="100%"></rect>
</svg>

View file

@ -0,0 +1,23 @@
<div class="flex items-start max-md:flex-col">
<div class="me-10 w-full pb-4 md:w-[220px]">
<flux:navlist>
<flux:navlist.item :href="route('profile.edit')" wire:navigate>{{ __('Profile') }}</flux:navlist.item>
<flux:navlist.item :href="route('user-password.edit')" wire:navigate>{{ __('Password') }}</flux:navlist.item>
@if (Laravel\Fortify\Features::canManageTwoFactorAuthentication())
<flux:navlist.item :href="route('two-factor.show')" wire:navigate>{{ __('Two-Factor Auth') }}</flux:navlist.item>
@endif
<flux:navlist.item :href="route('appearance.edit')" wire:navigate>{{ __('Appearance') }}</flux:navlist.item>
</flux:navlist>
</div>
<flux:separator class="md:hidden" />
<div class="flex-1 self-stretch max-md:pt-6">
<flux:heading>{{ $heading ?? '' }}</flux:heading>
<flux:subheading>{{ $subheading ?? '' }}</flux:subheading>
<div class="mt-5 w-full max-w-lg">
{{ $slot }}
</div>
</div>
</div>

View file

@ -0,0 +1,16 @@
<x-layouts::home>
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<h1 class="text-2xl font-bold mb-6">Contact Us</h1>
<form action="{{ route('contactus.store') }}" method="post" class="space-y-4">
@csrf
<flux:input name="name" label="Name" :value="old('name')" />
<flux:input name="surname" label="Surname" :value="old('surname')" />
<flux:input name="email" label="Email" :value="old('email')" />
<flux:button type="submit" variant="primary">@lang('Save')</flux:button>
</form>
@if (isset($message) && $message)
<flux:callout variant="success" icon="check-circle" heading="{{ $message }}" class="mt-6" />
@endif
</main>
</x-layouts::home>

View file

@ -0,0 +1,18 @@
<x-layouts::app :title="__('Dashboard')">
<div class="flex h-full w-full flex-1 flex-col gap-4 rounded-xl">
<div class="grid auto-rows-min gap-4 md:grid-cols-3">
<div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
<x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
</div>
<div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
<x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
</div>
<div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
<x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
</div>
</div>
<div class="relative h-full flex-1 overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
<x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
</div>
</div>
</x-layouts::app>

View file

@ -0,0 +1,47 @@
{{-- Credit: Lucide (https://lucide.dev) --}}
@props([
'variant' => 'outline',
])
@php
if ($variant === 'solid') {
throw new \Exception('The "solid" variant is not supported in Lucide.');
}
$classes = Flux::classes('shrink-0')->add(
match ($variant) {
'outline' => '[:where(&)]:size-6',
'solid' => '[:where(&)]:size-6',
'mini' => '[:where(&)]:size-5',
'micro' => '[:where(&)]:size-4',
},
);
$strokeWidth = match ($variant) {
'outline' => 2,
'mini' => 2.25,
'micro' => 2.5,
};
@endphp
<svg
{{ $attributes->class($classes) }}
data-flux-icon
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="{{ $strokeWidth }}"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
data-slot="icon"
>
<path d="M12 7v14" />
<path d="M16 12h2" />
<path d="M16 8h2" />
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" />
<path d="M6 12h2" />
<path d="M6 8h2" />
</svg>

View file

@ -0,0 +1,43 @@
{{-- Credit: Lucide (https://lucide.dev) --}}
@props([
'variant' => 'outline',
])
@php
if ($variant === 'solid') {
throw new \Exception('The "solid" variant is not supported in Lucide.');
}
$classes = Flux::classes('shrink-0')->add(
match ($variant) {
'outline' => '[:where(&)]:size-6',
'solid' => '[:where(&)]:size-6',
'mini' => '[:where(&)]:size-5',
'micro' => '[:where(&)]:size-4',
},
);
$strokeWidth = match ($variant) {
'outline' => 2,
'mini' => 2.25,
'micro' => 2.5,
};
@endphp
<svg
{{ $attributes->class($classes) }}
data-flux-icon
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="{{ $strokeWidth }}"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
data-slot="icon"
>
<path d="m7 15 5 5 5-5" />
<path d="m7 9 5-5 5 5" />
</svg>

View file

@ -0,0 +1,45 @@
{{-- Credit: Lucide (https://lucide.dev) --}}
@props([
'variant' => 'outline',
])
@php
if ($variant === 'solid') {
throw new \Exception('The "solid" variant is not supported in Lucide.');
}
$classes = Flux::classes('shrink-0')->add(
match ($variant) {
'outline' => '[:where(&)]:size-6',
'solid' => '[:where(&)]:size-6',
'mini' => '[:where(&)]:size-5',
'micro' => '[:where(&)]:size-4',
},
);
$strokeWidth = match ($variant) {
'outline' => 2,
'mini' => 2.25,
'micro' => 2.5,
};
@endphp
<svg
{{ $attributes->class($classes) }}
data-flux-icon
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="{{ $strokeWidth }}"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
data-slot="icon"
>
<path d="M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5" />
<circle cx="13" cy="12" r="2" />
<path d="M18 19c-2.8 0-5-2.2-5-5v8" />
<circle cx="20" cy="19" r="2" />
</svg>

View file

@ -0,0 +1,45 @@
{{-- Credit: Lucide (https://lucide.dev) --}}
@props([
'variant' => 'outline',
])
@php
if ($variant === 'solid') {
throw new \Exception('The "solid" variant is not supported in Lucide.');
}
$classes = Flux::classes('shrink-0')->add(
match ($variant) {
'outline' => '[:where(&)]:size-6',
'solid' => '[:where(&)]:size-6',
'mini' => '[:where(&)]:size-5',
'micro' => '[:where(&)]:size-4',
},
);
$strokeWidth = match ($variant) {
'outline' => 2,
'mini' => 2.25,
'micro' => 2.5,
};
@endphp
<svg
{{ $attributes->class($classes) }}
data-flux-icon
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="{{ $strokeWidth }}"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
data-slot="icon"
>
<rect width="7" height="7" x="3" y="3" rx="1" />
<rect width="7" height="7" x="14" y="3" rx="1" />
<rect width="7" height="7" x="14" y="14" rx="1" />
<rect width="7" height="7" x="3" y="14" rx="1" />
</svg>

View file

@ -0,0 +1,51 @@
@props([
'expandable' => false,
'expanded' => true,
'heading' => null,
])
<?php if ($expandable && $heading): ?>
<ui-disclosure
{{ $attributes->class('group/disclosure') }}
@if ($expanded === true) open @endif
data-flux-navlist-group
>
<button
type="button"
class="group/disclosure-button mb-[2px] flex h-10 w-full items-center rounded-lg text-zinc-500 hover:bg-zinc-800/5 hover:text-zinc-800 lg:h-8 dark:text-white/80 dark:hover:bg-white/[7%] dark:hover:text-white"
>
<div class="ps-3 pe-4">
<flux:icon.chevron-down class="hidden size-3! group-data-open/disclosure-button:block" />
<flux:icon.chevron-right class="block size-3! group-data-open/disclosure-button:hidden" />
</div>
<span class="text-sm font-medium leading-none">{{ $heading }}</span>
</button>
<div class="relative hidden space-y-[2px] ps-7 data-open:block" @if ($expanded === true) data-open @endif>
<div class="absolute inset-y-[3px] start-0 ms-4 w-px bg-zinc-200 dark:bg-white/30"></div>
{{ $slot }}
</div>
</ui-disclosure>
<?php elseif ($heading): ?>
<div {{ $attributes->class('block space-y-[2px]') }}>
<div class="px-1 py-2">
<div class="text-xs leading-none text-zinc-400">{{ $heading }}</div>
</div>
<div>
{{ $slot }}
</div>
</div>
<?php else: ?>
<div {{ $attributes->class('block space-y-[2px]') }}>
{{ $slot }}
</div>
<?php endif; ?>

View file

@ -0,0 +1,34 @@
<x-layouts::home>
<header class="relative bg-white pt-12 pb-16 overflow-hidden">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<livewire:posts.featured/>
</div>
</header>
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<div class="flex justify-between items-end mb-12">
<div>
<h2 class="text-3xl font-bold text-gray-900">Recent Stories</h2>
<p class="text-gray-500 mt-2">Deep dives into the latest technology trends.</p>
</div>
<div class="hidden sm:block">
<a href="#" class="text-indigo-600 font-semibold hover:text-indigo-700 flex items-center group">
View all
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1 group-hover:translate-x-1 transition"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</a>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
@php $posts = App\Models\Post::with('author', 'category')->orderBy('published_at', 'desc')->take(4)->get(); $posts->shift(); @endphp
@foreach ($posts as $post)
<livewire:posts.article :post="$post" />
@endforeach
</div>
<livewire:subscribe />
</main>
</x-layouts::home>

View file

@ -0,0 +1,7 @@
<x-layouts::app.header :title="$title ?? null">
<flux:main>
<div class="mx-auto w-full max-w-7xl">
{{ $slot }}
</div>
</flux:main>
</x-layouts::app.header>

Some files were not shown because too many files have changed in this diff Show more