diff --git a/.github/actions/setup-laravel/action.yml b/.github/actions/setup-laravel/action.yml
new file mode 100644
index 00000000..fb27885c
--- /dev/null
+++ b/.github/actions/setup-laravel/action.yml
@@ -0,0 +1,65 @@
+name: 'Setup Laravel test environment'
+description: 'Set up PHP, Composer, optional Node, the app key, database, and Passport keys for a test job.'
+
+inputs:
+ node:
+ description: 'Also set up Node.js and install npm dependencies.'
+ default: 'false'
+ db-port:
+ description: 'Host port mapped to the Postgres service.'
+ required: true
+ redis-port:
+ description: 'Host port mapped to the Redis service.'
+ required: true
+
+runs:
+ using: 'composite'
+ steps:
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.4'
+ extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, intl, gd, redis
+ coverage: none
+
+ - name: Setup Node.js
+ if: inputs.node == 'true'
+ uses: actions/setup-node@v6
+ with:
+ node-version: '22'
+ cache: 'npm'
+
+ - name: Cache Composer dependencies
+ uses: actions/cache@v6
+ with:
+ path: vendor
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: Prepare environment
+ shell: bash
+ run: cp .env.ci .env
+
+ - name: Install Composer dependencies
+ shell: bash
+ run: composer install --no-interaction --prefer-dist --optimize-autoloader
+
+ - name: Install npm dependencies
+ if: inputs.node == 'true'
+ shell: bash
+ run: npm ci
+
+ - name: Generate application key
+ shell: bash
+ run: php artisan key:generate
+
+ - name: Run migrations
+ shell: bash
+ env:
+ DB_PORT: ${{ inputs.db-port }}
+ REDIS_PORT: ${{ inputs.redis-port }}
+ run: php artisan migrate --force
+
+ - name: Generate Passport keys
+ shell: bash
+ run: php artisan passport:keys --force
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 09d1c471..c55a1127 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -17,7 +17,7 @@ jobs:
quality:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
- name: Setup PHP
uses: shivammathur/setup-php@v2
diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml
index 9558ca02..16a22ec6 100644
--- a/.github/workflows/release-docker.yml
+++ b/.github/workflows/release-docker.yml
@@ -36,7 +36,7 @@ jobs:
platform: ${{ matrix.platform }}
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
- name: Docker metadata (labels)
id: meta
@@ -76,7 +76,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
@@ -92,7 +92,7 @@ jobs:
packages: write
steps:
- name: Download digests
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v7
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index e733fb0f..497efe6d 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -6,18 +6,19 @@ on:
pull_request:
branches: [main, develop]
+env:
+ DB_CONNECTION: pgsql
+ DB_DATABASE: trypost_test
+ DB_USERNAME: postgres
+ DB_PASSWORD: password
+ BROADCAST_CONNECTION: "null"
+ CACHE_STORE: array
+ QUEUE_CONNECTION: sync
+ SESSION_DRIVER: array
+
jobs:
- tests:
+ backend:
runs-on: ubuntu-latest
- env:
- DB_CONNECTION: pgsql
- DB_DATABASE: trypost_test
- DB_USERNAME: postgres
- DB_PASSWORD: password
- BROADCAST_CONNECTION: "null"
- CACHE_STORE: array
- QUEUE_CONNECTION: sync
- SESSION_DRIVER: array
services:
postgres:
@@ -46,54 +47,89 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
+ - name: Setup test environment
+ uses: ./.github/actions/setup-laravel
with:
- php-version: '8.4'
- extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, intl, gd, redis
- coverage: none
+ db-port: ${{ job.services.postgres.ports['5432'] }}
+ redis-port: ${{ job.services.redis.ports['6379'] }}
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: '22'
- cache: 'npm'
+ - name: Run backend tests
+ env:
+ DB_PORT: ${{ job.services.postgres.ports['5432'] }}
+ REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
+ run: php artisan test --compact --parallel
- - name: Cache Composer dependencies
- uses: actions/cache@v4
- with:
- path: vendor
- key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
- restore-keys: ${{ runner.os }}-composer-
+ e2e:
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ shard: [1, 2]
- - name: Prepare environment
- run: cp .env.ci .env
+ services:
+ postgres:
+ image: postgres:16
+ env:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: password
+ POSTGRES_DB: trypost_test
+ ports:
+ - 5432/tcp
+ options: >-
+ --health-cmd="pg_isready"
+ --health-interval=10s
+ --health-timeout=5s
+ --health-retries=3
- - name: Install Composer dependencies
- run: composer install --no-interaction --prefer-dist --optimize-autoloader
+ redis:
+ image: redis:7
+ ports:
+ - 6379/tcp
+ options: >-
+ --health-cmd="redis-cli ping"
+ --health-interval=10s
+ --health-timeout=5s
+ --health-retries=3
- - name: Install npm dependencies
- run: npm ci
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v7
- - name: Generate application key
- run: php artisan key:generate
+ - name: Setup test environment
+ uses: ./.github/actions/setup-laravel
+ with:
+ node: 'true'
+ db-port: ${{ job.services.postgres.ports['5432'] }}
+ redis-port: ${{ job.services.redis.ports['6379'] }}
- name: Build assets
run: npm run build
- - name: Run migrations
- run: php artisan migrate --force
- env:
- DB_PORT: ${{ job.services.postgres.ports['5432'] }}
- REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
-
- - name: Generate Passport keys
- run: php artisan passport:keys --force
+ - name: Install Playwright browsers
+ run: npx playwright install --with-deps chromium
- - name: Run tests
- run: php artisan test --compact --parallel
+ - name: Run browser tests
env:
DB_PORT: ${{ job.services.postgres.ports['5432'] }}
REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
+ run: php artisan test tests/Browser --compact --shard=${{ matrix.shard }}/2
+
+ - name: Upload Playwright artifacts
+ if: failure()
+ uses: actions/upload-artifact@v7
+ with:
+ name: playwright-artifacts-${{ matrix.shard }}
+ path: tests/Browser/Screenshots
+ if-no-files-found: ignore
+ retention-days: 7
+
+ e2e-gate:
+ if: always()
+ needs: e2e
+ runs-on: ubuntu-latest
+ steps:
+ - name: Require all e2e shards to pass
+ run: '[ "${{ needs.e2e.result }}" = "success" ] || exit 1'
diff --git a/.gitignore b/.gitignore
index 508a4046..e61ad860 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
/.phpunit.cache
/bootstrap/ssr
/node_modules
+/tests/Browser/Screenshots
/public/build
/lang/php_*.json
/public/hot
diff --git a/composer.json b/composer.json
index 5768bda8..8d3a7946 100644
--- a/composer.json
+++ b/composer.json
@@ -74,6 +74,7 @@
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"pestphp/pest": "^4.4",
+ "pestphp/pest-plugin-browser": "^4.3",
"pestphp/pest-plugin-laravel": "^4.1"
},
"autoload": {
@@ -120,6 +121,10 @@
"@test:lint",
"@php artisan test"
],
+ "test:all": [
+ "@test",
+ "@php artisan test tests/Browser"
+ ],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
diff --git a/composer.lock b/composer.lock
index e556996b..5c17b705 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "f2f0d0ee195cb8181f595e304a6cfce0",
+ "content-hash": "25153dfd602f4770eee4612fef9e2857",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -10905,6 +10905,1236 @@
}
],
"packages-dev": [
+ {
+ "name": "amphp/amp",
+ "version": "v3.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/amp.git",
+ "reference": "2f3ebed5a4f663968a0590dbb7654a8b32cb63cb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/amp/zipball/2f3ebed5a4f663968a0590dbb7654a8b32cb63cb",
+ "reference": "2f3ebed5a4f663968a0590dbb7654a8b32cb63cb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "6.16.1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php",
+ "src/Future/functions.php",
+ "src/Internal/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Bob Weinand",
+ "email": "bobwei9@hotmail.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Daniel Lowrey",
+ "email": "rdlowrey@php.net"
+ }
+ ],
+ "description": "A non-blocking concurrency framework for PHP applications.",
+ "homepage": "https://amphp.org/amp",
+ "keywords": [
+ "async",
+ "asynchronous",
+ "awaitable",
+ "concurrency",
+ "event",
+ "event-loop",
+ "future",
+ "non-blocking",
+ "promise"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/amp/issues",
+ "source": "https://github.com/amphp/amp/tree/v3.1.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2026-06-21T13:59:44+00:00"
+ },
+ {
+ "name": "amphp/byte-stream",
+ "version": "v2.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/byte-stream.git",
+ "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46",
+ "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/parser": "^1.1",
+ "amphp/pipeline": "^1",
+ "amphp/serialization": "^1",
+ "amphp/sync": "^2",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2.3"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "5.22.1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php",
+ "src/Internal/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\ByteStream\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "A stream abstraction to make working with non-blocking I/O simple.",
+ "homepage": "https://amphp.org/byte-stream",
+ "keywords": [
+ "amp",
+ "amphp",
+ "async",
+ "io",
+ "non-blocking",
+ "stream"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/byte-stream/issues",
+ "source": "https://github.com/amphp/byte-stream/tree/v2.1.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2025-03-16T17:10:27+00:00"
+ },
+ {
+ "name": "amphp/cache",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/cache.git",
+ "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c",
+ "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/serialization": "^1",
+ "amphp/sync": "^2",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "^5.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Amp\\Cache\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Daniel Lowrey",
+ "email": "rdlowrey@php.net"
+ }
+ ],
+ "description": "A fiber-aware cache API based on Amp and Revolt.",
+ "homepage": "https://amphp.org/cache",
+ "support": {
+ "issues": "https://github.com/amphp/cache/issues",
+ "source": "https://github.com/amphp/cache/tree/v2.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-04-19T03:38:06+00:00"
+ },
+ {
+ "name": "amphp/dns",
+ "version": "v2.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/dns.git",
+ "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/dns/zipball/78eb3db5fc69bf2fc0cb503c4fcba667bc223c71",
+ "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/byte-stream": "^2",
+ "amphp/cache": "^2",
+ "amphp/parser": "^1",
+ "amphp/process": "^2",
+ "daverandom/libdns": "^2.0.2",
+ "ext-filter": "*",
+ "ext-json": "*",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "5.20"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Dns\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Wright",
+ "email": "addr@daverandom.com"
+ },
+ {
+ "name": "Daniel Lowrey",
+ "email": "rdlowrey@php.net"
+ },
+ {
+ "name": "Bob Weinand",
+ "email": "bobwei9@hotmail.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ }
+ ],
+ "description": "Async DNS resolution for Amp.",
+ "homepage": "https://github.com/amphp/dns",
+ "keywords": [
+ "amp",
+ "amphp",
+ "async",
+ "client",
+ "dns",
+ "resolve"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/dns/issues",
+ "source": "https://github.com/amphp/dns/tree/v2.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2025-01-19T15:43:40+00:00"
+ },
+ {
+ "name": "amphp/hpack",
+ "version": "v3.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/hpack.git",
+ "reference": "291da27078e7e149a9bad4d08ff05bf7d81c89f4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/hpack/zipball/291da27078e7e149a9bad4d08ff05bf7d81c89f4",
+ "reference": "291da27078e7e149a9bad4d08ff05bf7d81c89f4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "http2jp/hpack-test-case": "^1",
+ "nikic/php-fuzzer": "^0.0.11",
+ "phpunit/phpunit": "^7 | ^8 | ^9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Amp\\Http\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Daniel Lowrey",
+ "email": "rdlowrey@php.net"
+ },
+ {
+ "name": "Bob Weinand"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ }
+ ],
+ "description": "HTTP/2 HPack implementation.",
+ "homepage": "https://github.com/amphp/hpack",
+ "keywords": [
+ "headers",
+ "hpack",
+ "http-2"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/hpack/issues",
+ "source": "https://github.com/amphp/hpack/tree/v3.2.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2026-05-03T19:28:59+00:00"
+ },
+ {
+ "name": "amphp/http",
+ "version": "v2.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/http.git",
+ "reference": "3680d80bd38b5d6f3c2cef2214ca6dd6cef26588"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/http/zipball/3680d80bd38b5d6f3c2cef2214ca6dd6cef26588",
+ "reference": "3680d80bd38b5d6f3c2cef2214ca6dd6cef26588",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/hpack": "^3",
+ "amphp/parser": "^1.1",
+ "league/uri-components": "^2.4.2 | ^7.1",
+ "php": ">=8.1",
+ "psr/http-message": "^1 | ^2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "league/uri": "^6.8 | ^7.1",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "^5.26.1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php",
+ "src/Internal/constants.php"
+ ],
+ "psr-4": {
+ "Amp\\Http\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ }
+ ],
+ "description": "Basic HTTP primitives which can be shared by servers and clients.",
+ "support": {
+ "issues": "https://github.com/amphp/http/issues",
+ "source": "https://github.com/amphp/http/tree/v2.1.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-11-23T14:57:26+00:00"
+ },
+ {
+ "name": "amphp/http-client",
+ "version": "v5.3.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/http-client.git",
+ "reference": "ca155026acafa74a612d776a97202d53077fee86"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/http-client/zipball/ca155026acafa74a612d776a97202d53077fee86",
+ "reference": "ca155026acafa74a612d776a97202d53077fee86",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/byte-stream": "^2",
+ "amphp/hpack": "^3",
+ "amphp/http": "^2",
+ "amphp/pipeline": "^1",
+ "amphp/socket": "^2",
+ "amphp/sync": "^2",
+ "league/uri": "^7",
+ "league/uri-components": "^7",
+ "league/uri-interfaces": "^7.1",
+ "php": ">=8.1",
+ "psr/http-message": "^1 | ^2",
+ "revolt/event-loop": "^1"
+ },
+ "conflict": {
+ "amphp/file": "<3 | >=5"
+ },
+ "require-dev": {
+ "amphp/file": "^3 | ^4",
+ "amphp/http-server": "^3",
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "ext-json": "*",
+ "kelunik/link-header-rfc5988": "^1",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "6.16.1"
+ },
+ "suggest": {
+ "amphp/file": "Required for file request bodies and HTTP archive logging",
+ "ext-json": "Required for logging HTTP archives",
+ "ext-zlib": "Allows using compression for response bodies."
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php",
+ "src/Internal/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Http\\Client\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Daniel Lowrey",
+ "email": "rdlowrey@gmail.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ }
+ ],
+ "description": "An advanced async HTTP client library for PHP, enabling efficient, non-blocking, and concurrent requests and responses.",
+ "homepage": "https://amphp.org/http-client",
+ "keywords": [
+ "async",
+ "client",
+ "concurrent",
+ "http",
+ "non-blocking",
+ "rest"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/http-client/issues",
+ "source": "https://github.com/amphp/http-client/tree/v5.3.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2026-05-15T23:29:38+00:00"
+ },
+ {
+ "name": "amphp/http-server",
+ "version": "v3.4.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/http-server.git",
+ "reference": "8a971bf92cf8cf2bc511f37a75b39126d5305315"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/http-server/zipball/8a971bf92cf8cf2bc511f37a75b39126d5305315",
+ "reference": "8a971bf92cf8cf2bc511f37a75b39126d5305315",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/byte-stream": "^2",
+ "amphp/cache": "^2",
+ "amphp/hpack": "^3",
+ "amphp/http": "^2",
+ "amphp/pipeline": "^1",
+ "amphp/socket": "^2.1",
+ "amphp/sync": "^2.2",
+ "league/uri": "^7.1",
+ "league/uri-interfaces": "^7.1",
+ "php": ">=8.1",
+ "psr/http-message": "^1 | ^2",
+ "psr/log": "^1 | ^2 | ^3",
+ "revolt/event-loop": "^1"
+ },
+ "require-dev": {
+ "amphp/http-client": "^5",
+ "amphp/log": "^2",
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "league/uri-components": "^7.1",
+ "monolog/monolog": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "6.16.1"
+ },
+ "suggest": {
+ "ext-zlib": "Allows GZip compression of response bodies"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/Driver/functions.php",
+ "src/Middleware/functions.php",
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Http\\Server\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Daniel Lowrey",
+ "email": "rdlowrey@php.net"
+ },
+ {
+ "name": "Bob Weinand"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ }
+ ],
+ "description": "A non-blocking HTTP application server for PHP based on Amp.",
+ "homepage": "https://github.com/amphp/http-server",
+ "keywords": [
+ "amp",
+ "amphp",
+ "async",
+ "http",
+ "non-blocking",
+ "server"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/http-server/issues",
+ "source": "https://github.com/amphp/http-server/tree/v3.4.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2026-06-27T10:31:48+00:00"
+ },
+ {
+ "name": "amphp/parser",
+ "version": "v1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/parser.git",
+ "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7",
+ "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "^5.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Amp\\Parser\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "A generator parser to make streaming parsers simple.",
+ "homepage": "https://github.com/amphp/parser",
+ "keywords": [
+ "async",
+ "non-blocking",
+ "parser",
+ "stream"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/parser/issues",
+ "source": "https://github.com/amphp/parser/tree/v1.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-21T19:16:53+00:00"
+ },
+ {
+ "name": "amphp/pipeline",
+ "version": "v1.2.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/pipeline.git",
+ "reference": "92f121dde31cd1d89d5d0f9eba64ac40271b236e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/pipeline/zipball/92f121dde31cd1d89d5d0f9eba64ac40271b236e",
+ "reference": "92f121dde31cd1d89d5d0f9eba64ac40271b236e",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "6.16.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Amp\\Pipeline\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Asynchronous iterators and operators.",
+ "homepage": "https://amphp.org/pipeline",
+ "keywords": [
+ "amp",
+ "amphp",
+ "async",
+ "io",
+ "iterator",
+ "non-blocking"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/pipeline/issues",
+ "source": "https://github.com/amphp/pipeline/tree/v1.2.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2026-06-27T14:17:20+00:00"
+ },
+ {
+ "name": "amphp/process",
+ "version": "v2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/process.git",
+ "reference": "583959df17d00304ad7b0b32285373f985935643"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/process/zipball/583959df17d00304ad7b0b32285373f985935643",
+ "reference": "583959df17d00304ad7b0b32285373f985935643",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/byte-stream": "^2",
+ "amphp/sync": "^2",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "6.16.1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Process\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bob Weinand",
+ "email": "bobwei9@hotmail.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "A fiber-aware process manager based on Amp and Revolt.",
+ "homepage": "https://amphp.org/process",
+ "support": {
+ "issues": "https://github.com/amphp/process/issues",
+ "source": "https://github.com/amphp/process/tree/v2.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2026-05-31T15:11:55+00:00"
+ },
+ {
+ "name": "amphp/serialization",
+ "version": "v1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/serialization.git",
+ "reference": "fdf2834d78cebb0205fb2672676c1b1eb84371f0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/serialization/zipball/fdf2834d78cebb0205fb2672676c1b1eb84371f0",
+ "reference": "fdf2834d78cebb0205fb2672676c1b1eb84371f0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "ext-json": "*",
+ "ext-zlib": "*",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "6.16.1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Serialization\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Serialization tools for IPC and data storage in PHP.",
+ "homepage": "https://github.com/amphp/serialization",
+ "keywords": [
+ "async",
+ "asynchronous",
+ "serialization",
+ "serialize"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/serialization/issues",
+ "source": "https://github.com/amphp/serialization/tree/v1.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2026-04-05T15:59:53+00:00"
+ },
+ {
+ "name": "amphp/socket",
+ "version": "v2.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/socket.git",
+ "reference": "dadb63c5d3179fd83803e29dfeac27350e619314"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/socket/zipball/dadb63c5d3179fd83803e29dfeac27350e619314",
+ "reference": "dadb63c5d3179fd83803e29dfeac27350e619314",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/byte-stream": "^2",
+ "amphp/dns": "^2",
+ "ext-openssl": "*",
+ "kelunik/certificate": "^1.1",
+ "league/uri": "^7",
+ "league/uri-interfaces": "^7",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "amphp/process": "^2",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "6.16.1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php",
+ "src/Internal/functions.php",
+ "src/SocketAddress/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Socket\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Daniel Lowrey",
+ "email": "rdlowrey@gmail.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Non-blocking socket connection / server implementations based on Amp and Revolt.",
+ "homepage": "https://github.com/amphp/socket",
+ "keywords": [
+ "amp",
+ "async",
+ "encryption",
+ "non-blocking",
+ "sockets",
+ "tcp",
+ "tls"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/socket/issues",
+ "source": "https://github.com/amphp/socket/tree/v2.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2026-04-19T15:09:56+00:00"
+ },
+ {
+ "name": "amphp/sync",
+ "version": "v2.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/sync.git",
+ "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1",
+ "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/pipeline": "^1",
+ "amphp/serialization": "^1",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "5.23"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Sync\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Stephen Coakley",
+ "email": "me@stephencoakley.com"
+ }
+ ],
+ "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.",
+ "homepage": "https://github.com/amphp/sync",
+ "keywords": [
+ "async",
+ "asynchronous",
+ "mutex",
+ "semaphore",
+ "synchronization"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/sync/issues",
+ "source": "https://github.com/amphp/sync/tree/v2.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-03T19:31:26+00:00"
+ },
+ {
+ "name": "amphp/websocket",
+ "version": "v2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/websocket.git",
+ "reference": "963904b6a883c4b62d9222d1d9749814fac96a3b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/websocket/zipball/963904b6a883c4b62d9222d1d9749814fac96a3b",
+ "reference": "963904b6a883c4b62d9222d1d9749814fac96a3b",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/byte-stream": "^2",
+ "amphp/parser": "^1",
+ "amphp/pipeline": "^1",
+ "amphp/socket": "^2",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "^5.18"
+ },
+ "suggest": {
+ "ext-zlib": "Required for compression"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Websocket\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Bob Weinand",
+ "email": "bobwei9@hotmail.com"
+ }
+ ],
+ "description": "Shared code for websocket servers and clients.",
+ "homepage": "https://github.com/amphp/websocket",
+ "keywords": [
+ "amp",
+ "amphp",
+ "async",
+ "http",
+ "non-blocking",
+ "websocket"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/websocket/issues",
+ "source": "https://github.com/amphp/websocket/tree/v2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-28T21:28:45+00:00"
+ },
+ {
+ "name": "amphp/websocket-client",
+ "version": "v2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/websocket-client.git",
+ "reference": "dc033fdce0af56295a23f63ac4f579b34d470d6c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/websocket-client/zipball/dc033fdce0af56295a23f63ac4f579b34d470d6c",
+ "reference": "dc033fdce0af56295a23f63ac4f579b34d470d6c",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/byte-stream": "^2.1",
+ "amphp/http": "^2.1",
+ "amphp/http-client": "^5",
+ "amphp/socket": "^2.2",
+ "amphp/websocket": "^2",
+ "league/uri": "^7.1",
+ "php": ">=8.1",
+ "psr/http-message": "^1|^2",
+ "revolt/event-loop": "^1"
+ },
+ "require-dev": {
+ "amphp/http-server": "^3",
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "amphp/websocket-server": "^3|^4",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "~5.26.1",
+ "psr/log": "^1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Websocket\\Client\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bob Weinand",
+ "email": "bobwei9@hotmail.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Async WebSocket client for PHP based on Amp.",
+ "keywords": [
+ "amp",
+ "amphp",
+ "async",
+ "client",
+ "http",
+ "non-blocking",
+ "websocket"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/websocket-client/issues",
+ "source": "https://github.com/amphp/websocket-client/tree/v2.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2025-08-24T17:25:34+00:00"
+ },
{
"name": "brianium/paratest",
"version": "v7.20.0",
@@ -11140,6 +12370,50 @@
],
"time": "2024-05-06T16:37:16+00:00"
},
+ {
+ "name": "daverandom/libdns",
+ "version": "v2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/DaveRandom/LibDNS.git",
+ "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a",
+ "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "Required for IDN support"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "LibDNS\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "DNS protocol implementation written in pure PHP",
+ "keywords": [
+ "dns"
+ ],
+ "support": {
+ "issues": "https://github.com/DaveRandom/LibDNS/issues",
+ "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0"
+ },
+ "time": "2024-04-12T12:12:48+00:00"
+ },
{
"name": "doctrine/deprecations",
"version": "1.1.6",
@@ -11494,6 +12768,64 @@
},
"time": "2025-03-19T14:43:43+00:00"
},
+ {
+ "name": "kelunik/certificate",
+ "version": "v1.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/kelunik/certificate.git",
+ "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/kelunik/certificate/zipball/7e00d498c264d5eb4f78c69f41c8bd6719c0199e",
+ "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-openssl": "*",
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "phpunit/phpunit": "^6 | 7 | ^8 | ^9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Kelunik\\Certificate\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Access certificate details and transform between different formats.",
+ "keywords": [
+ "DER",
+ "certificate",
+ "certificates",
+ "openssl",
+ "pem",
+ "x509"
+ ],
+ "support": {
+ "issues": "https://github.com/kelunik/certificate/issues",
+ "source": "https://github.com/kelunik/certificate/tree/v1.1.3"
+ },
+ "time": "2023-02-03T21:26:53+00:00"
+ },
{
"name": "laravel/pail",
"version": "v1.2.7",
@@ -11774,6 +13106,90 @@
},
"time": "2026-04-06T12:52:26+00:00"
},
+ {
+ "name": "league/uri-components",
+ "version": "7.8.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/uri-components.git",
+ "reference": "848ff9db2f0be06229d6034b7c2e33d41b4fd675"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/uri-components/zipball/848ff9db2f0be06229d6034b7c2e33d41b4fd675",
+ "reference": "848ff9db2f0be06229d6034b7c2e33d41b4fd675",
+ "shasum": ""
+ },
+ "require": {
+ "league/uri": "^7.8.1",
+ "php": "^8.1"
+ },
+ "suggest": {
+ "ext-bcmath": "to improve IPV4 host parsing",
+ "ext-fileinfo": "to create Data URI from file contennts",
+ "ext-gmp": "to improve IPV4 host parsing",
+ "ext-intl": "to handle IDN host with the best performance",
+ "ext-mbstring": "to use the sorting algorithm of URLSearchParams",
+ "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain",
+ "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP",
+ "php-64bit": "to improve IPV4 host parsing",
+ "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
+ "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "7.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Uri\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignace Nyamagana Butera",
+ "email": "nyamsprod@gmail.com",
+ "homepage": "https://nyamsprod.com"
+ }
+ ],
+ "description": "URI components manipulation library",
+ "homepage": "http://uri.thephpleague.com",
+ "keywords": [
+ "authority",
+ "components",
+ "fragment",
+ "host",
+ "middleware",
+ "modifier",
+ "path",
+ "port",
+ "query",
+ "rfc3986",
+ "scheme",
+ "uri",
+ "url",
+ "userinfo"
+ ],
+ "support": {
+ "docs": "https://uri.thephpleague.com",
+ "forum": "https://thephpleague.slack.com",
+ "issues": "https://github.com/thephpleague/uri-src/issues",
+ "source": "https://github.com/thephpleague/uri-components/tree/7.8.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/nyamsprod",
+ "type": "github"
+ }
+ ],
+ "time": "2026-03-15T20:22:25+00:00"
+ },
{
"name": "mockery/mockery",
"version": "1.6.12",
@@ -12272,6 +13688,89 @@
],
"time": "2026-04-10T17:20:19+00:00"
},
+ {
+ "name": "pestphp/pest-plugin-browser",
+ "version": "v4.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/pestphp/pest-plugin-browser.git",
+ "reference": "b6e76d3e4a2f81da9f050ec54be2a29b402287c4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/pestphp/pest-plugin-browser/zipball/b6e76d3e4a2f81da9f050ec54be2a29b402287c4",
+ "reference": "b6e76d3e4a2f81da9f050ec54be2a29b402287c4",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3.1.1",
+ "amphp/http-server": "^3.4.4",
+ "amphp/websocket-client": "^2.0.2",
+ "ext-sockets": "*",
+ "pestphp/pest": "^4.4.5",
+ "pestphp/pest-plugin": "^4.0.0",
+ "php": "^8.3",
+ "symfony/process": "^7.4.8|^8.0.5"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "ext-posix": "*",
+ "livewire/livewire": "^3.7.15",
+ "nunomaduro/collision": "^8.9.3",
+ "orchestra/testbench": "^10.11.0",
+ "pestphp/pest-dev-tools": "^4.1.0",
+ "pestphp/pest-plugin-laravel": "^4.1",
+ "pestphp/pest-plugin-type-coverage": "^4.0.4"
+ },
+ "type": "library",
+ "extra": {
+ "pest": {
+ "plugins": [
+ "Pest\\Browser\\Plugin"
+ ]
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Autoload.php"
+ ],
+ "psr-4": {
+ "Pest\\Browser\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Pest plugin to test browser interactions",
+ "keywords": [
+ "browser",
+ "framework",
+ "pest",
+ "php",
+ "test",
+ "testing",
+ "unit"
+ ],
+ "support": {
+ "source": "https://github.com/pestphp/pest-plugin-browser/tree/v4.3.1"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/nunomaduro",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/nunomaduro",
+ "type": "patreon"
+ }
+ ],
+ "time": "2026-04-08T21:04:12+00:00"
+ },
{
"name": "pestphp/pest-plugin-laravel",
"version": "v4.1.0",
@@ -13207,6 +14706,78 @@
],
"time": "2026-06-04T06:14:42+00:00"
},
+ {
+ "name": "revolt/event-loop",
+ "version": "v1.0.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/revoltphp/event-loop.git",
+ "reference": "44061cf513e53c6200372fc935ac42271566295d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/44061cf513e53c6200372fc935ac42271566295d",
+ "reference": "44061cf513e53c6200372fc935ac42271566295d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "ext-json": "*",
+ "jetbrains/phpstorm-stubs": "^2019.3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "6.16.*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Revolt\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "ceesjank@gmail.com"
+ },
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Rock-solid event loop for concurrent PHP applications.",
+ "keywords": [
+ "async",
+ "asynchronous",
+ "concurrency",
+ "event",
+ "event-loop",
+ "non-blocking",
+ "scheduler"
+ ],
+ "support": {
+ "issues": "https://github.com/revoltphp/event-loop/issues",
+ "source": "https://github.com/revoltphp/event-loop/tree/v1.0.9"
+ },
+ "time": "2026-05-16T17:55:38+00:00"
+ },
{
"name": "sebastian/cli-parser",
"version": "4.2.1",
diff --git a/lang/ar/common.php b/lang/ar/common.php
index 59fd7803..e99f2356 100644
--- a/lang/ar/common.php
+++ b/lang/ar/common.php
@@ -21,6 +21,12 @@
'uploading' => 'جارٍ الرفع...',
'remove' => 'إزالة الصورة',
'hint' => 'موصى به: صورة مربعة، بحد أقصى 2 ميغابايت.',
+ 'crop_title' => 'قص الصورة',
+ 'crop_description' => 'حدّد المنطقة التي تريد الاحتفاظ بها.',
+ 'crop_hint' => 'اسحب للتحريك، أو زاوية لتغيير الحجم.',
+ 'crop_save' => 'حفظ',
+ 'crop_cancel' => 'إلغاء',
+ 'crop_error' => 'تعذّر تحميل هذه الصورة. جرّب ملفًا آخر.',
],
'timezone' => [
diff --git a/lang/de/common.php b/lang/de/common.php
index ce786c16..4e6d4b99 100644
--- a/lang/de/common.php
+++ b/lang/de/common.php
@@ -21,6 +21,12 @@
'uploading' => 'Wird hochgeladen...',
'remove' => 'Foto entfernen',
'hint' => 'Empfohlen: quadratisches Bild, max. 2 MB.',
+ 'crop_title' => 'Bild zuschneiden',
+ 'crop_description' => 'Wählen Sie den Bereich aus, den Sie behalten möchten.',
+ 'crop_hint' => 'Zum Verschieben ziehen oder an einer Ecke die Größe ändern.',
+ 'crop_save' => 'Speichern',
+ 'crop_cancel' => 'Abbrechen',
+ 'crop_error' => 'Dieses Bild konnte nicht geladen werden. Versuchen Sie eine andere Datei.',
],
'timezone' => [
diff --git a/lang/el/common.php b/lang/el/common.php
index 0bb7db23..c18cb1a1 100644
--- a/lang/el/common.php
+++ b/lang/el/common.php
@@ -21,6 +21,12 @@
'uploading' => 'Μεταφόρτωση...',
'remove' => 'Αφαίρεση φωτογραφίας',
'hint' => 'Συνιστάται: τετράγωνη εικόνα, έως 2 MB.',
+ 'crop_title' => 'Περικοπή εικόνας',
+ 'crop_description' => 'Επιλέξτε την περιοχή που θέλετε να κρατήσετε.',
+ 'crop_hint' => 'Σύρετε για μετακίνηση ή μια γωνία για αλλαγή μεγέθους.',
+ 'crop_save' => 'Αποθήκευση',
+ 'crop_cancel' => 'Άκυρο',
+ 'crop_error' => 'Δεν ήταν δυνατή η φόρτωση αυτής της εικόνας. Δοκιμάστε άλλο αρχείο.',
],
'timezone' => [
diff --git a/lang/en/common.php b/lang/en/common.php
index a41a2d55..6bda1664 100644
--- a/lang/en/common.php
+++ b/lang/en/common.php
@@ -21,6 +21,12 @@
'uploading' => 'Uploading...',
'remove' => 'Remove photo',
'hint' => 'Recommended: square image, max 2 MB.',
+ 'crop_title' => 'Crop image',
+ 'crop_description' => 'Select the area you want to keep.',
+ 'crop_hint' => 'Drag to move, or a corner to resize.',
+ 'crop_save' => 'Save',
+ 'crop_cancel' => 'Cancel',
+ 'crop_error' => 'Couldn\'t load this image. Try another file.',
],
'timezone' => [
diff --git a/lang/es/common.php b/lang/es/common.php
index fe84cf9b..2a532649 100644
--- a/lang/es/common.php
+++ b/lang/es/common.php
@@ -21,6 +21,12 @@
'uploading' => 'Subiendo...',
'remove' => 'Eliminar foto',
'hint' => 'Recomendado: imagen cuadrada, máximo 2 MB.',
+ 'crop_title' => 'Recortar imagen',
+ 'crop_description' => 'Selecciona el área que quieres conservar.',
+ 'crop_hint' => 'Arrastra para mover o una esquina para redimensionar.',
+ 'crop_save' => 'Guardar',
+ 'crop_cancel' => 'Cancelar',
+ 'crop_error' => 'No se pudo cargar esta imagen. Prueba con otro archivo.',
],
'timezone' => [
diff --git a/lang/fr/common.php b/lang/fr/common.php
index 206a805d..2531f3ea 100644
--- a/lang/fr/common.php
+++ b/lang/fr/common.php
@@ -21,6 +21,12 @@
'uploading' => 'Import en cours...',
'remove' => 'Supprimer la photo',
'hint' => 'Recommandé : image carrée, 2 Mo maximum.',
+ 'crop_title' => 'Recadrer l\'image',
+ 'crop_description' => 'Sélectionnez la zone à conserver.',
+ 'crop_hint' => 'Faites glisser pour déplacer, ou un coin pour redimensionner.',
+ 'crop_save' => 'Enregistrer',
+ 'crop_cancel' => 'Annuler',
+ 'crop_error' => 'Impossible de charger cette image. Essayez un autre fichier.',
],
'timezone' => [
diff --git a/lang/it/common.php b/lang/it/common.php
index 44785808..1d7cf87a 100644
--- a/lang/it/common.php
+++ b/lang/it/common.php
@@ -21,6 +21,12 @@
'uploading' => 'Caricamento in corso...',
'remove' => 'Rimuovi foto',
'hint' => 'Consigliato: immagine quadrata, max 2 MB.',
+ 'crop_title' => 'Ritaglia immagine',
+ 'crop_description' => 'Seleziona l\'area da mantenere.',
+ 'crop_hint' => 'Trascina per spostare o un angolo per ridimensionare.',
+ 'crop_save' => 'Salva',
+ 'crop_cancel' => 'Annulla',
+ 'crop_error' => 'Impossibile caricare questa immagine. Prova un altro file.',
],
'timezone' => [
diff --git a/lang/ja/common.php b/lang/ja/common.php
index 96e89257..539f41fe 100644
--- a/lang/ja/common.php
+++ b/lang/ja/common.php
@@ -21,6 +21,12 @@
'uploading' => 'アップロード中...',
'remove' => '写真を削除',
'hint' => '推奨: 正方形の画像、最大 2 MB。',
+ 'crop_title' => '画像を切り抜く',
+ 'crop_description' => '残したい範囲を選択してください。',
+ 'crop_hint' => 'ドラッグで移動、角でサイズ変更できます。',
+ 'crop_save' => '保存',
+ 'crop_cancel' => 'キャンセル',
+ 'crop_error' => 'この画像を読み込めませんでした。別のファイルをお試しください。',
],
'timezone' => [
diff --git a/lang/ko/common.php b/lang/ko/common.php
index 5bb2d175..ef309ea0 100644
--- a/lang/ko/common.php
+++ b/lang/ko/common.php
@@ -21,6 +21,12 @@
'uploading' => '업로드 중...',
'remove' => '사진 제거',
'hint' => '권장: 정사각형 이미지, 최대 2MB.',
+ 'crop_title' => '이미지 자르기',
+ 'crop_description' => '남길 영역을 선택하세요.',
+ 'crop_hint' => '드래그하여 이동하거나 모서리로 크기를 조절하세요.',
+ 'crop_save' => '저장',
+ 'crop_cancel' => '취소',
+ 'crop_error' => '이 이미지를 불러올 수 없습니다. 다른 파일을 사용해 보세요.',
],
'timezone' => [
diff --git a/lang/nl/common.php b/lang/nl/common.php
index 7fb1ead5..6046682e 100644
--- a/lang/nl/common.php
+++ b/lang/nl/common.php
@@ -21,6 +21,12 @@
'uploading' => 'Uploaden...',
'remove' => 'Foto verwijderen',
'hint' => 'Aanbevolen: vierkante afbeelding, max. 2 MB.',
+ 'crop_title' => 'Afbeelding bijsnijden',
+ 'crop_description' => 'Selecteer het gebied dat je wilt behouden.',
+ 'crop_hint' => 'Sleep om te verplaatsen of een hoek om te vergroten of verkleinen.',
+ 'crop_save' => 'Opslaan',
+ 'crop_cancel' => 'Annuleren',
+ 'crop_error' => 'Kan deze afbeelding niet laden. Probeer een ander bestand.',
],
'timezone' => [
diff --git a/lang/pl/common.php b/lang/pl/common.php
index ed7a3fe1..d4b0e2db 100644
--- a/lang/pl/common.php
+++ b/lang/pl/common.php
@@ -21,6 +21,12 @@
'uploading' => 'Przesyłanie...',
'remove' => 'Usuń zdjęcie',
'hint' => 'Zalecane: kwadratowe zdjęcie, maks. 2 MB.',
+ 'crop_title' => 'Przytnij obraz',
+ 'crop_description' => 'Zaznacz obszar, który chcesz zachować.',
+ 'crop_hint' => 'Przeciągnij, aby przesunąć, lub róg, aby zmienić rozmiar.',
+ 'crop_save' => 'Zapisz',
+ 'crop_cancel' => 'Anuluj',
+ 'crop_error' => 'Nie można załadować tego obrazu. Spróbuj innego pliku.',
],
'timezone' => [
diff --git a/lang/pt-BR/common.php b/lang/pt-BR/common.php
index a64d8a34..5c41d709 100644
--- a/lang/pt-BR/common.php
+++ b/lang/pt-BR/common.php
@@ -21,6 +21,12 @@
'uploading' => 'Enviando...',
'remove' => 'Remover foto',
'hint' => 'Recomendado: imagem quadrada, máximo 2 MB.',
+ 'crop_title' => 'Cortar imagem',
+ 'crop_description' => 'Selecione a área que deseja manter.',
+ 'crop_hint' => 'Arraste para mover ou um canto para redimensionar.',
+ 'crop_save' => 'Salvar',
+ 'crop_cancel' => 'Cancelar',
+ 'crop_error' => 'Não foi possível carregar esta imagem. Tente outro arquivo.',
],
'timezone' => [
diff --git a/lang/ru/common.php b/lang/ru/common.php
index 6b7e39fd..aecc5a2c 100644
--- a/lang/ru/common.php
+++ b/lang/ru/common.php
@@ -21,6 +21,12 @@
'uploading' => 'Загрузка...',
'remove' => 'Удалить фото',
'hint' => 'Рекомендуется: квадратное изображение, до 2 МБ.',
+ 'crop_title' => 'Обрезать изображение',
+ 'crop_description' => 'Выберите область, которую нужно оставить.',
+ 'crop_hint' => 'Перетащите, чтобы переместить, или угол — чтобы изменить размер.',
+ 'crop_save' => 'Сохранить',
+ 'crop_cancel' => 'Отмена',
+ 'crop_error' => 'Не удалось загрузить это изображение. Попробуйте другой файл.',
],
'timezone' => [
diff --git a/lang/tr/common.php b/lang/tr/common.php
index 294ef152..f73011a5 100644
--- a/lang/tr/common.php
+++ b/lang/tr/common.php
@@ -21,6 +21,12 @@
'uploading' => 'Yükleniyor...',
'remove' => 'Fotoğrafı kaldır',
'hint' => 'Önerilen: kare görsel, en fazla 2 MB.',
+ 'crop_title' => 'Görseli kırp',
+ 'crop_description' => 'Saklamak istediğiniz alanı seçin.',
+ 'crop_hint' => 'Taşımak için sürükleyin veya boyutlandırmak için bir köşeyi sürükleyin.',
+ 'crop_save' => 'Kaydet',
+ 'crop_cancel' => 'İptal',
+ 'crop_error' => 'Bu görsel yüklenemedi. Başka bir dosya deneyin.',
],
'timezone' => [
diff --git a/lang/zh/common.php b/lang/zh/common.php
index b16eb072..1eeb850b 100644
--- a/lang/zh/common.php
+++ b/lang/zh/common.php
@@ -21,6 +21,12 @@
'uploading' => '上传中…',
'remove' => '移除照片',
'hint' => '推荐:正方形图片,最大 2 MB。',
+ 'crop_title' => '裁剪图片',
+ 'crop_description' => '选择要保留的区域。',
+ 'crop_hint' => '拖动以移动,拖动边角可调整大小。',
+ 'crop_save' => '保存',
+ 'crop_cancel' => '取消',
+ 'crop_error' => '无法加载此图片。请尝试其他文件。',
],
'timezone' => [
diff --git a/package-lock.json b/package-lock.json
index 3941c5b3..51f067e3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -55,6 +55,7 @@
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-vue": "^9.32.0",
"laravel-echo": "^2.3.0",
+ "playwright": "^1.61.1",
"prettier": "^3.4.2",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-tailwindcss": "^0.6.11",
@@ -1448,9 +1449,6 @@
"cpu": [
"arm64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1467,9 +1465,6 @@
"cpu": [
"arm64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1486,9 +1481,6 @@
"cpu": [
"ppc64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1505,9 +1497,6 @@
"cpu": [
"s390x"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1524,9 +1513,6 @@
"cpu": [
"x64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1543,9 +1529,6 @@
"cpu": [
"x64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1855,9 +1838,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1875,9 +1855,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1894,9 +1871,6 @@
"cpu": [
"x64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1914,9 +1888,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -7204,9 +7175,6 @@
"cpu": [
"arm64"
],
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7227,9 +7195,6 @@
"cpu": [
"arm64"
],
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7250,9 +7215,6 @@
"cpu": [
"x64"
],
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7273,9 +7235,6 @@
"cpu": [
"x64"
],
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -7866,6 +7825,53 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.61.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz",
+ "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.61.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.61.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz",
+ "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
diff --git a/package.json b/package.json
index 9f650655..5920e3a0 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-vue": "^9.32.0",
"laravel-echo": "^2.3.0",
+ "playwright": "^1.61.1",
"prettier": "^3.4.2",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-tailwindcss": "^0.6.11",
diff --git a/resources/js/components/ImageCropperDialog.vue b/resources/js/components/ImageCropperDialog.vue
new file mode 100644
index 00000000..90b2fb2f
--- /dev/null
+++ b/resources/js/components/ImageCropperDialog.vue
@@ -0,0 +1,368 @@
+
+
+
+
+
diff --git a/resources/js/components/PhotoUpload.vue b/resources/js/components/PhotoUpload.vue
index 3be75a6f..65c99b7e 100644
--- a/resources/js/components/PhotoUpload.vue
+++ b/resources/js/components/PhotoUpload.vue
@@ -2,8 +2,9 @@
import { router } from '@inertiajs/vue3';
import { IconTrash } from '@tabler/icons-vue';
import { trans } from 'laravel-vue-i18n';
-import { ref } from 'vue';
+import { ref, watch } from 'vue';
+import ImageCropperDialog from '@/components/ImageCropperDialog.vue';
import { Avatar } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import {
@@ -34,6 +35,17 @@ const props = withDefaults(defineProps