forked from lubiana/futtern
Compare commits
12 commits
main
...
bootystrap
Author | SHA1 | Date | |
---|---|---|---|
f530902648 | |||
a1a7a9a146 | |||
af2bab6221 | |||
a0f6e63032 | |||
26ca9ac17a | |||
f2c6196d89 | |||
fdbbbc3bd6 | |||
730402e7ee | |||
7c6ef15552 | |||
101cdb5d10 | |||
9c4648eb94 | |||
321173dfe6 |
43 changed files with 11439 additions and 2004 deletions
4
.env.dev
4
.env.dev
|
@ -1,4 +0,0 @@
|
||||||
|
|
||||||
###> symfony/framework-bundle ###
|
|
||||||
APP_SECRET=11c8937d48993fb3aee1a476413161f5
|
|
||||||
###< symfony/framework-bundle ###
|
|
|
@ -2,36 +2,38 @@ on: [pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
ls:
|
ls:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
|
||||||
image: git.php.fail/lubiana/container/php:8.4.8-ci
|
|
||||||
steps:
|
steps:
|
||||||
- name: Manually checkout
|
- name: Checkout code
|
||||||
env:
|
uses: actions/checkout@v4
|
||||||
REPO: '${{ github.repository }}'
|
|
||||||
TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
- name: Setup PHP 8.4
|
||||||
GIT_SERVER: 'git.hannover.ccc.de'
|
uses: https://github.com/shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.4'
|
||||||
|
extensions: mbstring, ctype, iconv, pdo, dom, fileinfo, intl, zip
|
||||||
|
tools: composer:v2
|
||||||
|
|
||||||
|
- name: Get Composer cache directory
|
||||||
|
id: composer-cache
|
||||||
run: |
|
run: |
|
||||||
git clone --branch $GITHUB_HEAD_REF https://${TOKEN}@${GIT_SERVER}/${REPO}.git .
|
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
git fetch
|
|
||||||
git checkout $GITHUB_HEAD_REF
|
- name: Cache Composer dependencies
|
||||||
- name: composer install
|
uses: actions/cache@v4
|
||||||
env:
|
with:
|
||||||
COMPOSER_CACHE_DIR: /opt/hostedtoolcache/.composer/cache/files
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
run: |
|
key: php-8.4-composer-${{ hashFiles('**/composer.lock') }}
|
||||||
mkdir -p ${{ env.COMPOSER_CACHE_DIR }}
|
restore-keys: |
|
||||||
composer install
|
php-8.4-composer-
|
||||||
- name: lint
|
|
||||||
run: composer lint
|
- name: Clean up invalid Composer GitHub token
|
||||||
- name: test
|
run: composer config --global --unset github-oauth.github.com || true
|
||||||
run: composer test
|
|
||||||
- name: GIT commit and push all changed files
|
- name: Install dependencies
|
||||||
env:
|
run: composer install --prefer-dist --no-progress --no-scripts
|
||||||
CI_COMMIT_MESSAGE: Continuous Integration Fixes
|
|
||||||
CI_COMMIT_AUTHOR: Continuous Integration
|
- name: Run Symfony auto-scripts
|
||||||
run: |
|
run: composer run-script post-install-cmd
|
||||||
if [[ -n "$(git status -s)" ]]; then
|
|
||||||
git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}"
|
- name: Run PestPHP tests (parallel)
|
||||||
git config --global user.email "gitbot@users.noreply.php.fail"
|
run: composer test
|
||||||
git commit -am "${{ env.CI_COMMIT_MESSAGE }}"
|
|
||||||
git push
|
|
||||||
fi
|
|
|
@ -5,9 +5,12 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
ls:
|
ls:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
|
||||||
image: git.php.fail/lubiana/container/php:8.4.8-ci
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: setup php
|
||||||
|
uses: https://github.com/shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.4'
|
||||||
|
tools: 'composer'
|
||||||
- name: Manually checkout
|
- name: Manually checkout
|
||||||
env:
|
env:
|
||||||
REPO: '${{ github.repository }}'
|
REPO: '${{ github.repository }}'
|
||||||
|
@ -38,4 +41,4 @@ jobs:
|
||||||
git config --global user.email "gitbot@users.noreply.php.fail"
|
git config --global user.email "gitbot@users.noreply.php.fail"
|
||||||
git commit -am "${{ env.CI_COMMIT_MESSAGE }}"
|
git commit -am "${{ env.CI_COMMIT_MESSAGE }}"
|
||||||
git push
|
git push
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -4,8 +4,13 @@ jobs:
|
||||||
ls:
|
ls:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: git.php.fail/lubiana/container/php:8.4.8-ci
|
image: php:8.4-cli
|
||||||
steps:
|
steps:
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y git openssh-client rsync
|
||||||
|
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
- name: Manually checkout
|
- name: Manually checkout
|
||||||
env:
|
env:
|
||||||
REPO: '${{ github.repository }}'
|
REPO: '${{ github.repository }}'
|
||||||
|
@ -43,7 +48,3 @@ jobs:
|
||||||
rsync -avz --delete deploy/ ${USERNAME}@${HOST}:${TARGETDIR} --exclude=var
|
rsync -avz --delete deploy/ ${USERNAME}@${HOST}:${TARGETDIR} --exclude=var
|
||||||
# run update script
|
# run update script
|
||||||
ssh ${USERNAME}@${HOST} /home/c3h-futtern/futtern/update.sh
|
ssh ${USERNAME}@${HOST} /home/c3h-futtern/futtern/update.sh
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -18,11 +18,9 @@
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
###> squizlabs/php_codesniffer ###
|
###> symfony/webpack-encore-bundle ###
|
||||||
/.phpcs-cache
|
/node_modules/
|
||||||
/phpcs.xml
|
/public/build/
|
||||||
###< squizlabs/php_codesniffer ###
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
###> phpstan/phpstan ###
|
###< symfony/webpack-encore-bundle ###
|
||||||
phpstan.neon
|
|
||||||
###< phpstan/phpstan ###
|
|
||||||
|
|
111
.junie/guidelines.md
Normal file
111
.junie/guidelines.md
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
# Development Guidelines for Futtern
|
||||||
|
|
||||||
|
This document provides essential information for developers working on the Futtern project.
|
||||||
|
|
||||||
|
## Build and Configuration
|
||||||
|
|
||||||
|
### PHP Requirements
|
||||||
|
- PHP 8.4 or higher
|
||||||
|
- Required extensions: ctype, iconv
|
||||||
|
|
||||||
|
### Database Configuration
|
||||||
|
- Default: SQLite (`var/data.db`)
|
||||||
|
- Can be configured for MySQL or PostgreSQL by updating the `DATABASE_URL` in `.env` or `.env.local`
|
||||||
|
|
||||||
|
### Installation Steps
|
||||||
|
1. Clone the repository
|
||||||
|
2. Install PHP dependencies:
|
||||||
|
```bash
|
||||||
|
composer install
|
||||||
|
```
|
||||||
|
3. Install Node.js dependencies:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
4. Copy static assets to Encore directory:
|
||||||
|
```bash
|
||||||
|
mkdir -p assets/css assets/js
|
||||||
|
cp public/static/css/water.min.css assets/css/
|
||||||
|
cp public/static/js/htmx.min.js assets/js/
|
||||||
|
```
|
||||||
|
5. Build frontend assets:
|
||||||
|
```bash
|
||||||
|
npm run dev # For development
|
||||||
|
npm run build # For production
|
||||||
|
```
|
||||||
|
6. Create database schema:
|
||||||
|
```bash
|
||||||
|
php bin/console doctrine:schema:create
|
||||||
|
```
|
||||||
|
7. Load fixtures (optional):
|
||||||
|
```bash
|
||||||
|
php bin/console doctrine:fixtures:load
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
- Run `npm run watch` to automatically rebuild assets when files change
|
||||||
|
- Use Symfony CLI for local development: `symfony server:start`
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Test Configuration
|
||||||
|
- Tests use Pest PHP (built on PHPUnit)
|
||||||
|
- Test environment is configured in `.env.test`
|
||||||
|
- Tests are organized in `tests/Feature` and `tests/Unit` directories
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
- Run all tests: `composer test`
|
||||||
|
- Run specific tests: `composer test -- --filter=TestName`
|
||||||
|
- Run tests in parallel: `composer test` (parallel is the default)
|
||||||
|
|
||||||
|
### Test Types
|
||||||
|
1. **Unit Tests** (`tests/Unit/`):
|
||||||
|
- Test individual classes in isolation
|
||||||
|
- Extend `TestCase` or use standalone Pest tests
|
||||||
|
- Example:
|
||||||
|
```php
|
||||||
|
test('Favicon returns SVG string', function (): void {
|
||||||
|
$favicon = new Favicon();
|
||||||
|
$result = (string)$favicon;
|
||||||
|
|
||||||
|
expect($result)
|
||||||
|
->toBeString()
|
||||||
|
->toContain('data:image/svg+xml')
|
||||||
|
->toContain('transform=\'rotate(')
|
||||||
|
->toContain('viewBox=\'0 0 512 512\'');
|
||||||
|
})
|
||||||
|
->covers(Favicon::class);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Feature Tests** (`tests/Feature/`):
|
||||||
|
- Controller tests: Extend `DbWebTest` for web controllers
|
||||||
|
- API tests: Extend `DbApiTestCase` for API endpoints
|
||||||
|
- Each test gets a fresh database
|
||||||
|
|
||||||
|
### Creating New Tests
|
||||||
|
1. Determine the appropriate test type (Unit or Feature)
|
||||||
|
2. Create a new test file in the corresponding directory
|
||||||
|
3. Use Pest's `test()` function to define tests
|
||||||
|
4. Use `expect()` for assertions
|
||||||
|
5. Use `->covers()` to specify which classes are covered by the test
|
||||||
|
|
||||||
|
## Code Quality
|
||||||
|
|
||||||
|
### Code Style
|
||||||
|
- The project uses Easy Coding Standard (ECS) for code style checking
|
||||||
|
- Configuration is in `ecs.php`
|
||||||
|
- All classes should be marked as `final` unless they're meant to be extended
|
||||||
|
- Run code style checks: `composer lint`
|
||||||
|
|
||||||
|
### Code Quality Tools
|
||||||
|
- Rector is used for automated code refactoring
|
||||||
|
- Configuration is in `rector.php`
|
||||||
|
- Run code quality checks: `composer lint`
|
||||||
|
|
||||||
|
### Development Practices
|
||||||
|
- Use strict types in all PHP files: `<?php declare(strict_types=1);`
|
||||||
|
- Follow PSR-12 coding standards
|
||||||
|
- Use type hints for all method parameters and return types
|
||||||
|
- Use constructor property promotion where appropriate
|
||||||
|
- Use readonly properties where appropriate
|
||||||
|
- Use PHP 8.4 features where appropriate
|
35
README.md
Normal file
35
README.md
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# Asset Management with Webpack Encore
|
||||||
|
|
||||||
|
This project uses Webpack Encore for asset management including JavaScript and CSS minification.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Before using the application, you need to set up the front-end assets:
|
||||||
|
|
||||||
|
1. Install Node.js dependencies:
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Copy the existing static assets to the Encore assets directory:
|
||||||
|
```
|
||||||
|
mkdir -p assets/css assets/js
|
||||||
|
cp public/static/css/water.min.css assets/css/
|
||||||
|
cp public/static/js/htmx.min.js assets/js/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build the assets:
|
||||||
|
|
||||||
|
For development:
|
||||||
|
```
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
For production (with minification):
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
During development, you can use the watch command to automatically rebuild assets when files change:
|
11
assets/app.js
Normal file
11
assets/app.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* Welcome to your app's main JavaScript file!
|
||||||
|
*
|
||||||
|
* We recommend including the built version of this JavaScript file
|
||||||
|
* (and its CSS file) in your base layout (base.html.twig).
|
||||||
|
*/
|
||||||
|
|
||||||
|
// any CSS you import will output into a single css file (app.css in this case)
|
||||||
|
import './styles/app.scss';
|
||||||
|
|
||||||
|
import * as bootstrap from 'bootstrap';
|
4
assets/styles/app.scss
Normal file
4
assets/styles/app.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/* Import water.min.css */
|
||||||
|
@import "bootstrap/scss/bootstrap.scss";
|
||||||
|
|
||||||
|
/* You can add your other custom styles here */
|
|
@ -7,45 +7,46 @@
|
||||||
"php": ">=8.4",
|
"php": ">=8.4",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"api-platform/doctrine-orm": "*",
|
"api-platform/doctrine-orm": "^4.1.12",
|
||||||
"api-platform/symfony": "*",
|
"api-platform/symfony": "4.1.4",
|
||||||
"doctrine/dbal": "^4.2.3",
|
"doctrine/dbal": "^4.2.3",
|
||||||
"doctrine/doctrine-bundle": "^2.14.1",
|
"doctrine/doctrine-bundle": "^2.14",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.4.2",
|
"doctrine/doctrine-migrations-bundle": "^3.4.2",
|
||||||
"doctrine/orm": "^3.4.0",
|
"doctrine/orm": "^3.3.3",
|
||||||
"nelmio/cors-bundle": "^2.5",
|
"nelmio/cors-bundle": "^2.5",
|
||||||
"phpdocumentor/reflection-docblock": "^5.6.2",
|
"phpdocumentor/reflection-docblock": "^5.6.2",
|
||||||
"phpstan/phpdoc-parser": "^1.33",
|
"phpstan/phpdoc-parser": "^1.33",
|
||||||
"psr/clock": "^1.0",
|
"psr/clock": "^1.0",
|
||||||
"symfony/asset": "7.3.*",
|
"symfony/asset": "7.2.*",
|
||||||
"symfony/console": "7.3.*",
|
"symfony/console": "7.1.*",
|
||||||
"symfony/dotenv": "7.3.*",
|
"symfony/dotenv": "7.1.*",
|
||||||
"symfony/expression-language": "7.3.*",
|
"symfony/expression-language": "7.2.*",
|
||||||
"symfony/flex": "^2.7.1",
|
"symfony/flex": "^2.6.0",
|
||||||
"symfony/form": "7.3.*",
|
"symfony/form": "7.1.*",
|
||||||
"symfony/framework-bundle": "7.3.*",
|
"symfony/framework-bundle": "7.1.*",
|
||||||
"symfony/property-access": "7.3.*",
|
"symfony/property-access": "7.2.*",
|
||||||
"symfony/property-info": "7.3.*",
|
"symfony/property-info": "7.2.*",
|
||||||
"symfony/runtime": "7.3.*",
|
"symfony/runtime": "7.1.*",
|
||||||
"symfony/security-bundle": "7.3.*",
|
"symfony/security-bundle": "7.2.*",
|
||||||
"symfony/security-csrf": "7.3.*",
|
"symfony/security-csrf": "7.1.*",
|
||||||
"symfony/serializer": "7.3.*",
|
"symfony/serializer": "7.2.*",
|
||||||
"symfony/twig-bundle": "7.3.*",
|
"symfony/twig-bundle": "7.1.*",
|
||||||
"symfony/uid": "7.3.*",
|
"symfony/uid": "7.1.*",
|
||||||
"symfony/validator": "7.3.*",
|
"symfony/validator": "7.1.*",
|
||||||
"symfony/yaml": "7.3.*"
|
"symfony/webpack-encore-bundle": "^2.2",
|
||||||
|
"symfony/yaml": "7.1.*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
||||||
"liip/test-fixtures-bundle": "^3.4",
|
"liip/test-fixtures-bundle": "^3.3",
|
||||||
"lubiana/code-quality": "^1.7.2",
|
"lubiana/code-quality": "^1.7.2",
|
||||||
"pestphp/pest": "^3.8.2",
|
"pestphp/pest": "^3.6",
|
||||||
"symfony/browser-kit": "7.3.*",
|
"symfony/browser-kit": "7.2.*",
|
||||||
"symfony/css-selector": "7.3.*",
|
"symfony/css-selector": "7.2.*",
|
||||||
"symfony/http-client": "7.3.*",
|
"symfony/http-client": "7.2.*",
|
||||||
"symfony/maker-bundle": "^1.63",
|
"symfony/maker-bundle": "^1.63",
|
||||||
"symfony/stopwatch": "7.3.*",
|
"symfony/stopwatch": "7.2.*",
|
||||||
"symfony/web-profiler-bundle": "7.3.*",
|
"symfony/web-profiler-bundle": "7.2.*",
|
||||||
"symplify/config-transformer": "^12.4.0"
|
"symplify/config-transformer": "^12.4.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
@ -80,8 +81,7 @@
|
||||||
"symfony/polyfill-php80": "*",
|
"symfony/polyfill-php80": "*",
|
||||||
"symfony/polyfill-php81": "*",
|
"symfony/polyfill-php81": "*",
|
||||||
"symfony/polyfill-php82": "*",
|
"symfony/polyfill-php82": "*",
|
||||||
"symfony/polyfill-php83": "*",
|
"symfony/polyfill-php83": "*"
|
||||||
"symfony/polyfill-php84": "*"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"auto-scripts": {
|
"auto-scripts": {
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"allow-contrib": false,
|
"allow-contrib": false,
|
||||||
"require": "7.3.*"
|
"require": "7.2.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2457
composer.lock
generated
2457
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -11,6 +11,7 @@ use Symfony\Bundle\MakerBundle\MakerBundle;
|
||||||
use Symfony\Bundle\SecurityBundle\SecurityBundle;
|
use Symfony\Bundle\SecurityBundle\SecurityBundle;
|
||||||
use Symfony\Bundle\TwigBundle\TwigBundle;
|
use Symfony\Bundle\TwigBundle\TwigBundle;
|
||||||
use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
|
use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
|
||||||
|
use Symfony\WebpackEncoreBundle\WebpackEncoreBundle;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
FrameworkBundle::class => [
|
FrameworkBundle::class => [
|
||||||
|
@ -42,11 +43,14 @@ return [
|
||||||
NelmioCorsBundle::class => [
|
NelmioCorsBundle::class => [
|
||||||
'all' => true,
|
'all' => true,
|
||||||
],
|
],
|
||||||
|
ApiPlatformBundle::class => [
|
||||||
|
'all' => true,
|
||||||
|
],
|
||||||
LiipTestFixturesBundle::class => [
|
LiipTestFixturesBundle::class => [
|
||||||
'dev' => true,
|
'dev' => true,
|
||||||
'test' => true,
|
'test' => true,
|
||||||
],
|
],
|
||||||
ApiPlatformBundle::class => [
|
WebpackEncoreBundle::class => [
|
||||||
'all' => true,
|
'all' => true,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -4,8 +4,10 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||||
$containerConfigurator->extension('api_platform', [
|
$containerConfigurator->extension('api_platform', [
|
||||||
'title' => 'Hello API Platform',
|
'title' => 'Futtern API',
|
||||||
'version' => '1.0.0',
|
'version' => '1.0.0',
|
||||||
|
'show_webby' => false,
|
||||||
|
'enable_swagger' => true,
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'stateless' => true,
|
'stateless' => true,
|
||||||
'cache_headers' => [
|
'cache_headers' => [
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
|
||||||
$containerConfigurator->extension('framework', [
|
|
||||||
'form' => [
|
|
||||||
'csrf_protection' => [
|
|
||||||
'token_id' => 'submit',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'csrf_protection' => [
|
|
||||||
'stateless_token_ids' => [
|
|
||||||
'submit',
|
|
||||||
'authenticate',
|
|
||||||
'logout',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
};
|
|
|
@ -1,11 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
|
||||||
$containerConfigurator->extension('framework', [
|
|
||||||
'property_info' => [
|
|
||||||
'with_constructor_extractor' => true,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
};
|
|
|
@ -1,17 +1,13 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||||
|
use Symfony\Config\TwigConfig;
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
return static function (TwigConfig $twigConfig, ContainerConfigurator $c): void {
|
||||||
$containerConfigurator->extension('twig', [
|
$twigConfig->fileNamePattern('*.twig');
|
||||||
'file_name_pattern' => '*.twig',
|
$twigConfig->global('favicon', '@App\Service\Favicon');
|
||||||
'globals' => [
|
$twigConfig->formThemes(['bootstrap_5_layout.html.twig']);
|
||||||
'favicon' => '@App\Service\Favicon',
|
if ($c->env() === 'test') {
|
||||||
],
|
$twigConfig->strictVariables(true);
|
||||||
]);
|
|
||||||
if ($containerConfigurator->env() === 'test') {
|
|
||||||
$containerConfigurator->extension('twig', [
|
|
||||||
'strict_variables' => true,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
10
config/packages/webpack_encore.php
Normal file
10
config/packages/webpack_encore.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||||
|
use Symfony\Config\WebpackEncoreConfig;
|
||||||
|
|
||||||
|
return static function (WebpackEncoreConfig $webpackEncoreConfig, ContainerConfigurator $containerConfigurator): void {
|
||||||
|
$webpackEncoreConfig->outputPath('%kernel.project_dir%/public/build');
|
||||||
|
$webpackEncoreConfig->scriptAttributes('defer', true);
|
||||||
|
$webpackEncoreConfig->cache('%kernel.debug%');
|
||||||
|
};
|
|
@ -8,13 +8,15 @@ fi
|
||||||
mkdir $TARGETDIR
|
mkdir $TARGETDIR
|
||||||
cd $TARGETDIR || return
|
cd $TARGETDIR || return
|
||||||
|
|
||||||
pathsToCopy="public bin config migrations src templates composer.json composer.lock symfony.lock .env"
|
pathsToCopy="public bin config migrations src templates composer.json composer.lock symfony.lock .env assets package.json package.lock webpack.config.js"
|
||||||
|
|
||||||
for path in $pathsToCopy
|
for path in $pathsToCopy
|
||||||
do
|
do
|
||||||
cp -r ../../"$path" ./
|
cp -r ../../"$path" ./
|
||||||
done
|
done
|
||||||
|
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
APP_ENV=prod composer install --no-dev -a
|
APP_ENV=prod composer install --no-dev -a
|
||||||
rm -rf ./var/cache
|
rm -rf ./var/cache
|
||||||
|
|
||||||
|
|
9051
package-lock.json
generated
Normal file
9051
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
37
package.json
Normal file
37
package.json
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.24.0",
|
||||||
|
"@babel/preset-env": "^7.24.0",
|
||||||
|
"@symfony/webpack-encore": "^4.5.0",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
|
"core-js": "^3.36.0",
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"postcss-loader": "^8.1.1",
|
||||||
|
"regenerator-runtime": "^0.14.1",
|
||||||
|
"sass": "^1.87.0",
|
||||||
|
"sass-loader": "^14.2.1",
|
||||||
|
"style-loader": "^4.0.0",
|
||||||
|
"webpack": "^5.90.3",
|
||||||
|
"webpack-cli": "^5.1.4",
|
||||||
|
"webpack-notifier": "^1.15.0"
|
||||||
|
},
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev-server": "encore dev-server",
|
||||||
|
"dev": "encore dev",
|
||||||
|
"watch": "encore dev --watch",
|
||||||
|
"build": "encore production --progress"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 0.5%",
|
||||||
|
"last 2 versions",
|
||||||
|
"Firefox ESR",
|
||||||
|
"not dead"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"bootstrap": "^5.3.5"
|
||||||
|
}
|
||||||
|
}
|
20
php-styler.php
Normal file
20
php-styler.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
use PhpStyler\Config;
|
||||||
|
use PhpStyler\Files;
|
||||||
|
use PhpStyler\Styler;
|
||||||
|
|
||||||
|
return new Config(
|
||||||
|
styler: new Styler(lineLen: 79),
|
||||||
|
files: new Files(
|
||||||
|
__DIR__ . '/bin',
|
||||||
|
__DIR__ . '/public',
|
||||||
|
__DIR__ . '/src',
|
||||||
|
__DIR__ . '/config',
|
||||||
|
__DIR__ . '/tests',
|
||||||
|
__DIR__ . '/php-styler.php',
|
||||||
|
__DIR__ . '/ecs.php',
|
||||||
|
__DIR__ . '/rector.php',
|
||||||
|
),
|
||||||
|
cache: __DIR__ . '/.php-styler.cache',
|
||||||
|
);
|
|
@ -1,965 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
SPDX-FileCopyrightText: Copyright (c) 2022-2025 zichy
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Custom properties
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--f-sans: ui-sans-serif, sans-serif;
|
|
||||||
|
|
||||||
--f-body: ui-serif;
|
|
||||||
--f-heading: var(--f-sans);
|
|
||||||
--f-form: var(--f-sans);
|
|
||||||
--f-code: ui-monospace;
|
|
||||||
|
|
||||||
--f-size: clamp(1.6rem, 1.75vw, 2rem);
|
|
||||||
--f-size-small: 0.85em;
|
|
||||||
--f-size-large: 1.25em;
|
|
||||||
--f-line: 1.5;
|
|
||||||
|
|
||||||
--c-gray: #666;
|
|
||||||
--c-red: #b30;
|
|
||||||
--c-yellow: #fe9;
|
|
||||||
|
|
||||||
--i-triangle: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"%3E%3Cpolygon fill="black" points="5 10 10 0 0 0"/%3E%3C/svg%3E');
|
|
||||||
|
|
||||||
--w-body: 80ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark theme */
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--c-gray: #999;
|
|
||||||
--c-red: #f99;
|
|
||||||
--c-yellow: #ff9;
|
|
||||||
|
|
||||||
--i-triangle: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"%3E%3Cpolygon fill="white" points="5 10 10 0 0 0"/%3E%3C/svg%3E');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Globals
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Box sizing */
|
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Text rendering */
|
|
||||||
|
|
||||||
* {
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Interaction */
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
background: Highlight;
|
|
||||||
color: HighlightText;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
*:focus {
|
|
||||||
outline: 2px solid LinkText;
|
|
||||||
outline-offset: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Font size & Scrolling */
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 62.5%;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
scroll-padding-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Backdrop */
|
|
||||||
|
|
||||||
::backdrop {
|
|
||||||
background-color: rgba(255, 255, 255, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
::backdrop {
|
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hidden elements */
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print spacing */
|
|
||||||
|
|
||||||
@page {
|
|
||||||
margin: 15mm 20mm;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Body
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Colors & Typography */
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: Canvas;
|
|
||||||
color: CanvasText;
|
|
||||||
font-size: var(--f-size);
|
|
||||||
font-family: var(--f-body);
|
|
||||||
line-height: var(--f-line);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Body sizing */
|
|
||||||
|
|
||||||
@media screen {
|
|
||||||
body {
|
|
||||||
max-width: var(--w-body, 100%);
|
|
||||||
min-width: 320px;
|
|
||||||
padding: 2rem;
|
|
||||||
margin: 0 auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print colors */
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
body {
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Links
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
a:any-link {
|
|
||||||
color: LinkText;
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration-thickness: 0.125em;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:any-link:hover {
|
|
||||||
background-color: LinkText;
|
|
||||||
color: Canvas;
|
|
||||||
text-decoration-line: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
a[href^="http"]::after {
|
|
||||||
content: ' ('attr(href)')';
|
|
||||||
font-size: var(--f-size-small);
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Media
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Reset */
|
|
||||||
|
|
||||||
:where(iframe, img, svg, canvas, audio, video) {
|
|
||||||
display: block;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
:where(audio, video) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
figure {
|
|
||||||
margin-inline: 0;
|
|
||||||
break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Image */
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: auto;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
img::before {
|
|
||||||
content: '';
|
|
||||||
background-color: Highlight;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen {
|
|
||||||
picture img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Video */
|
|
||||||
|
|
||||||
video {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Iframe */
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Headings
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
:where(h1, h2, h3, h4, h5, h6) {
|
|
||||||
font-family: var(--f-heading);
|
|
||||||
line-height: calc(var(--f-line) / 1.25);
|
|
||||||
hyphens: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(h3, h5) {
|
|
||||||
color: var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(h4, h5, h6) {
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(h2, h3, h4, h5, h6):target {
|
|
||||||
background-color: var(--c-yellow);
|
|
||||||
color: MarkText;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Lists
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
:where(ul, ol) {
|
|
||||||
padding-inline-start: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: disc;
|
|
||||||
}
|
|
||||||
|
|
||||||
li::marker {
|
|
||||||
color: var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
li p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Description */
|
|
||||||
|
|
||||||
dt {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navigation */
|
|
||||||
|
|
||||||
nav ul {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem 2rem;
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
nav {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Inline elements
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Bold text */
|
|
||||||
|
|
||||||
:where(b, strong) {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Small text */
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: var(--f-size-small);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mark */
|
|
||||||
|
|
||||||
mark {
|
|
||||||
background-color: var(--c-yellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Abbreviation */
|
|
||||||
|
|
||||||
abbr[title] {
|
|
||||||
text-decoration-line: underline;
|
|
||||||
text-decoration-style: dotted;
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
|
|
||||||
a abbr[title] {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Subscript & Superscript */
|
|
||||||
|
|
||||||
:where(sub, sup) {
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Quote */
|
|
||||||
|
|
||||||
q {
|
|
||||||
font-style: italic;
|
|
||||||
quotes: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keyboard input */
|
|
||||||
|
|
||||||
kbd {
|
|
||||||
background: linear-gradient(0deg, Canvas 0%, ButtonFace 100%);
|
|
||||||
font-size: var(--f-size-small);
|
|
||||||
font-family: var(--f-sans);
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
box-shadow: 1px 1px 1px 0px var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Ruby annotation
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
rt {
|
|
||||||
color: var(--c-gray);
|
|
||||||
font-family: var(--f-sans);
|
|
||||||
letter-spacing: -0.05em;
|
|
||||||
padding: 0 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Horizontal rule
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
hr {
|
|
||||||
height: 0;
|
|
||||||
margin: 2em 0;
|
|
||||||
border: 0;
|
|
||||||
border-top: 2px solid var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Blockquote
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
font-size: var(--f-size-large);
|
|
||||||
font-style: italic;
|
|
||||||
line-height: calc(var(--f-line) / 1.25);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote > *:first-child {
|
|
||||||
margin-block-start: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote > *:last-child {
|
|
||||||
margin-block-end: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Captions
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
:where(caption, figcaption) {
|
|
||||||
color: var(--c-gray);
|
|
||||||
font-family: var(--f-heading);
|
|
||||||
font-size: var(--f-size-small);
|
|
||||||
font-style: italic;
|
|
||||||
margin-block-start: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
caption {
|
|
||||||
text-align: left;
|
|
||||||
caption-side: bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir='rtl' i] caption {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Code
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
:where(pre, code, samp, var) {
|
|
||||||
background-color: ButtonFace;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(code, samp, var) {
|
|
||||||
font-size: var(--f-size-small);
|
|
||||||
font-family: var(--f-code);
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
font-size: var(--f-size-small);
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen {
|
|
||||||
pre {
|
|
||||||
overflow-x: scroll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pre code {
|
|
||||||
background-color: transparent;
|
|
||||||
display: block;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Details
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
details {
|
|
||||||
background-color: ButtonFace;
|
|
||||||
padding: 2rem;
|
|
||||||
margin: 1em 0;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
details > *:nth-child(2) {
|
|
||||||
margin-block-start: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
details > *:last-child {
|
|
||||||
margin-block-end: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
color: LinkText;
|
|
||||||
font-family: var(--f-heading);
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
details[open] summary {
|
|
||||||
margin-block-end: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Aside
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
aside {
|
|
||||||
color: var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 769px) {
|
|
||||||
aside {
|
|
||||||
font-size: var(--f-size-small);
|
|
||||||
float: right;
|
|
||||||
width: calc(var(--w-body) / 2.5);
|
|
||||||
padding-block-end: 2rem;
|
|
||||||
padding-inline-start: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside > *:first-child {
|
|
||||||
margin-block-start: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside > *:last-child {
|
|
||||||
margin-block-end: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Table
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
margin: 1em 0;
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
table {
|
|
||||||
display: block;
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thead {
|
|
||||||
border-bottom: 2px solid var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr:nth-child(odd) {
|
|
||||||
background-color: ButtonFace;
|
|
||||||
}
|
|
||||||
|
|
||||||
tfoot {
|
|
||||||
border-top: 2px solid var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(th, td) {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
:where(th, td) {
|
|
||||||
min-width: 10rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
font-family: var(--f-heading);
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir='rtl' i] th {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Forms & Inputs
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Reset */
|
|
||||||
|
|
||||||
:where(input, textarea, select, button, progress) {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
background-color: transparent;
|
|
||||||
break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(input, textarea, select, button) {
|
|
||||||
font-family: var(--f-form);
|
|
||||||
font-size: 1em;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(input:not([type='button' i]):not([type='submit' i]):not([type='reset' i]):not([type='checkbox' i]):not([type='radio' i]):not([type='image' i]), textarea, select) {
|
|
||||||
color: CanvasText;
|
|
||||||
font-size: var(--f-size-small);
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border: 2px solid LinkText;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Placeholder */
|
|
||||||
|
|
||||||
::placeholder {
|
|
||||||
color: var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fieldset */
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
padding: 2rem;
|
|
||||||
border: 2px solid LinkText;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Label & Legend */
|
|
||||||
|
|
||||||
:where(legend, label) {
|
|
||||||
font-family: var(--f-form);
|
|
||||||
font-weight: bold;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
legend {
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(legend, label) small {
|
|
||||||
color: var(--c-gray);
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Textarea */
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Checkbox & Radio input */
|
|
||||||
|
|
||||||
label:has([type='checkbox' i], [type='radio' i]) {
|
|
||||||
font-family: var(--f-form);
|
|
||||||
font-size: var(--f-size-small);
|
|
||||||
font-weight: normal;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1.25em 1fr;
|
|
||||||
column-gap: 0.5em;
|
|
||||||
padding-block-end: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
label:has([type='checkbox' i][disabled], [type='radio' i][disabled]) {
|
|
||||||
color: var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
:where([type='checkbox' i], [type='radio' i]) {
|
|
||||||
width: 1.25em;
|
|
||||||
height: 1.25em;
|
|
||||||
position: relative;
|
|
||||||
margin: 0.2rem 0 0;
|
|
||||||
border: 2px solid LinkText;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
[type='radio' i] {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where([type='checkbox' i], [type='radio' i]):checked {
|
|
||||||
background-color: LinkText;
|
|
||||||
}
|
|
||||||
|
|
||||||
[type='checkbox' i]:checked::after {
|
|
||||||
content: '\2713';
|
|
||||||
color: Canvas;
|
|
||||||
font-family: var(--f-form);
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Color input */
|
|
||||||
|
|
||||||
[type='color' i] {
|
|
||||||
height: 4rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-color-swatch-wrapper {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-color-swatch {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-color-swatch {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Range input */
|
|
||||||
|
|
||||||
[type='range' i] {
|
|
||||||
margin: 1.25rem 0 0;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[type='range' i]:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-slider-runnable-track {
|
|
||||||
background-color: LinkText;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[disabled]::-webkit-slider-runnable-track {
|
|
||||||
background-color: var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-range-track {
|
|
||||||
background-color: LinkText;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[disabled]::-moz-range-track {
|
|
||||||
background-color: var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-slider-thumb {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
background-color: Canvas;
|
|
||||||
height: 2rem;
|
|
||||||
width: 2rem;
|
|
||||||
margin-block-start: calc(-1rem + 2px);
|
|
||||||
border: 2px solid LinkText;
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: ew-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
[disabled]::-webkit-slider-thumb {
|
|
||||||
border-color: var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
[type='range' i]:focus::-webkit-slider-thumb {
|
|
||||||
outline: 2px solid LinkText;
|
|
||||||
outline-offset: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-range-thumb {
|
|
||||||
appearance: none;
|
|
||||||
background-color: Canvas;
|
|
||||||
height: 2rem;
|
|
||||||
width: 2rem;
|
|
||||||
margin-block-start: calc(-1rem + 2px);
|
|
||||||
border: 2px solid LinkText;
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: ew-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
[disabled]::-moz-range-thumb {
|
|
||||||
border-color: var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
[type='range' i]:focus::-moz-range-thumb {
|
|
||||||
outline: 2px solid LinkText;
|
|
||||||
outline-offset: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Select */
|
|
||||||
|
|
||||||
select {
|
|
||||||
background: Canvas var(--i-triangle) no-repeat calc(100% - 1rem) center / 1.5rem;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
padding-inline-end: 3.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir='rtl' i] select {
|
|
||||||
background-position: 1rem center;
|
|
||||||
padding-inline: 3.5rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
select[multiple] {
|
|
||||||
background-image: none;
|
|
||||||
padding-inline-end: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buttons */
|
|
||||||
|
|
||||||
:where(button, [type='button' i], [type='submit' i], [type='reset' i]) {
|
|
||||||
font-size: var(--f-size-small);
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
line-height: 1;
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 5rem;
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
border: 2px solid LinkText;
|
|
||||||
-webkit-user-select: text;
|
|
||||||
user-select: text;
|
|
||||||
cursor: pointer;
|
|
||||||
touch-action: manipulation;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(button:not([disabled]), [type='button' i]:not([disabled]), [type='submit' i]:not([disabled]), [type='reset' i]:not([disabled])):hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen {
|
|
||||||
:where(button, [type='button' i], [type='submit' i], [type='reset' i]) {
|
|
||||||
background-color: LinkText;
|
|
||||||
color: Canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(button[disabled], [type='button' i][disabled], [type='submit' i][disabled], [type='reset' i][disabled]) {
|
|
||||||
background-color: var(--c-gray);
|
|
||||||
color: currentColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
form :where(button, [type='button' i], [type='submit' i], [type='reset' i]) {
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Meter & Progress */
|
|
||||||
|
|
||||||
:where(meter, progress) {
|
|
||||||
width: 100%;
|
|
||||||
height: 3rem;
|
|
||||||
border: 2px solid var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
label + :where(meter, progress) {
|
|
||||||
margin-block-start: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
meter {
|
|
||||||
background: transparent;
|
|
||||||
display: block;
|
|
||||||
margin-block-end: 1em;
|
|
||||||
border: 2px solid var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-meter-bar {
|
|
||||||
background: Canvas;
|
|
||||||
height: 3rem;
|
|
||||||
border: 2px solid var(--c-gray);
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-progress-bar {
|
|
||||||
background-color: Canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-progress-bar {
|
|
||||||
background-color: var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-progress-value {
|
|
||||||
background-color: var(--c-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Disabled state */
|
|
||||||
|
|
||||||
[disabled] {
|
|
||||||
border-color: var(--c-gray);
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Error state */
|
|
||||||
|
|
||||||
[aria-invalid] {
|
|
||||||
border-color: var(--c-red) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[aria-invalid]:focus {
|
|
||||||
outline-color: var(--c-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
[aria-invalid] + p[id] {
|
|
||||||
color: var(--c-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form spacing */
|
|
||||||
|
|
||||||
form label:not(:first-of-type) {
|
|
||||||
margin-block-start: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
form label + :where(input, textarea, select) {
|
|
||||||
margin-block-start: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
form fieldset {
|
|
||||||
margin: 3rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset label:not(:first-of-type) {
|
|
||||||
margin-block-start: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
form p[id] {
|
|
||||||
margin-block-start: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Dialog
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
dialog[open] {
|
|
||||||
background-color: Canvas;
|
|
||||||
color: currentColor;
|
|
||||||
display: block;
|
|
||||||
max-width: var(--w-body, 100%);
|
|
||||||
min-width: calc(var(--w-body) / 2);
|
|
||||||
padding: 2rem;
|
|
||||||
border: 2px solid var(--c-gray);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:has(dialog[open]) {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog:not([open]) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog > *:first-child {
|
|
||||||
margin-block-start: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog > *:last-child {
|
|
||||||
margin-block-end: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Opinionated layout
|
|
||||||
========================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
@media screen {
|
|
||||||
body > header {
|
|
||||||
margin-block-end: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > :where(section, article),
|
|
||||||
body > footer {
|
|
||||||
margin-block-start: 4em;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
body > footer {
|
|
||||||
margin-block-start: 4em;
|
|
||||||
}
|
|
||||||
}
|
|
1
public/static/css/new.min.css
vendored
1
public/static/css/new.min.css
vendored
|
@ -1 +0,0 @@
|
||||||
blockquote,header{background:var(--nc-bg-2)}dt,summary,table caption{font-weight:700}img,pre,textarea{max-width:100%}:root{--nc-font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--nc-font-mono:Consolas,monaco,'Ubuntu Mono','Liberation Mono','Courier New',Courier,monospace;--nc-tx-1:#000000;--nc-tx-2:#1A1A1A;--nc-bg-1:#FFFFFF;--nc-bg-2:#F6F8FA;--nc-bg-3:#E5E7EB;--nc-lk-1:#0070F3;--nc-lk-2:#0366D6;--nc-lk-tx:#FFFFFF;--nc-ac-1:#79FFE1;--nc-ac-tx:#0C4047;--nc-d-tx-1:#ffffff;--nc-d-tx-2:#eeeeee;--nc-d-bg-1:#000000;--nc-d-bg-2:#111111;--nc-d-bg-3:#222222;--nc-d-lk-1:#3291FF;--nc-d-lk-2:#0070F3;--nc-d-lk-tx:#FFFFFF;--nc-d-ac-1:#7928CA;--nc-d-ac-tx:#FFFFFF}@media (prefers-color-scheme:dark){:root{--nc-tx-1:var(--nc-d-tx-1);--nc-tx-2:var(--nc-d-tx-2);--nc-bg-1:var(--nc-d-bg-1);--nc-bg-2:var(--nc-d-bg-2);--nc-bg-3:var(--nc-d-bg-3);--nc-lk-1:var(--nc-d-lk-1);--nc-lk-2:var(--nc-d-lk-2);--nc-lk-tx:var(--nc--dlk-tx);--nc-ac-1:var(--nc-d-ac-1);--nc-ac-tx:var(--nc--dac-tx)}}*{margin:0;padding:0}address,area,article,aside,audio,blockquote,datalist,details,dl,fieldset,figure,form,iframe,img,input,meter,nav,ol,optgroup,option,output,p,pre,progress,ruby,section,table,textarea,ul,video{margin-bottom:1rem}button,html,input,select{font-family:var(--nc-font-sans)}body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:break-word;overflow-wrap:break-word;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}::selection{background:var(--nc-ac-1);color:var(--nc-ac-tx)}h1,h2,h3,h4,h5,h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}h1,h2,h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}h4,h5,h6{margin-bottom:.3rem}h1{font-size:2.25rem}h2{font-size:1.85rem}h3{font-size:1.55rem}h4{font-size:1.25rem}h5{font-size:1rem}h6{font-size:.875rem}a{color:var(--nc-lk-1)}a:hover{color:var(--nc-lk-2)}abbr,abbr:hover{cursor:help}blockquote{padding:1.5rem;border-left:5px solid var(--nc-bg-3)}blockquote :last-child{padding-bottom:0;margin-bottom:0}header{border-bottom:1px solid var(--nc-bg-3);padding:2rem 1.5rem;margin:-2rem calc(50% - 50vw) 2rem;padding-left:calc(50vw - 50%);padding-right:calc(50vw - 50%)}header h1,header h2,header h3{padding-bottom:0;border-bottom:0}header>:first-child{margin-top:0;padding-top:0}a img,details[open]>:last-child,header>:last-child,ol ol,ol ul,ul ol,ul ul{margin-bottom:0}a button,button,input[type=button],input[type=reset],input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:var(--nc-lk-1);color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}a button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{opacity:.5;cursor:not-allowed}.button:enabled:hover,.button:focus,button:enabled:hover,button:focus,input[type=button]:enabled:hover,input[type=button]:focus,input[type=reset]:enabled:hover,input[type=reset]:focus,input[type=submit]:enabled:hover,input[type=submit]:focus{background:var(--nc-lk-2)}code,details,input,kbd,pre,samp,select,textarea,th,tr:nth-child(2n){background:var(--nc-bg-2)}code,kbd,pre,samp{font-family:var(--nc-font-mono);border:1px solid var(--nc-bg-3);border-radius:4px;padding:3px 6px;font-size:.9em}code pre,pre code{background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}details,fieldset{border:1px solid var(--nc-bg-3)}kbd{border-bottom:3px solid var(--nc-bg-3)}pre{padding:1rem 1.4rem;overflow:auto}code pre{display:inline}details{padding:.6rem 1rem;border-radius:4px}summary{cursor:pointer}details[open]{padding-bottom:.75rem}details[open] summary{margin-bottom:6px}dd::before{content:'→ '}hr{border:0;border-bottom:1px solid var(--nc-bg-3);margin:1rem auto}fieldset{margin-top:1rem;padding:2rem;border-radius:4px}input,select,td,textarea,th{border:1px solid var(--nc-bg-3)}legend{padding:auto .5rem}table{border-collapse:collapse;width:100%}td,th{text-align:left;padding:.5rem}table caption{margin-bottom:.5rem}ol,ul{padding-left:2rem}li{margin-top:.4rem}mark{padding:3px 6px;background:var(--nc-ac-1);color:var(--nc-ac-tx)}input,select,textarea{padding:6px 12px;margin-bottom:.5rem;color:var(--nc-tx-2);border-radius:4px;box-shadow:none;box-sizing:border-box}
|
|
1
public/static/css/simple.min.css
vendored
1
public/static/css/simple.min.css
vendored
File diff suppressed because one or more lines are too long
1
public/static/css/water.min.css
vendored
1
public/static/css/water.min.css
vendored
File diff suppressed because one or more lines are too long
2
public/static/js/htmx.min.js
vendored
2
public/static/js/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -3,14 +3,9 @@
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Delete;
|
|
||||||
use ApiPlatform\Metadata\Get;
|
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
use ApiPlatform\Metadata\Post;
|
|
||||||
use ApiPlatform\Metadata\Put;
|
|
||||||
use App\Repository\FoodOrderRepository;
|
use App\Repository\FoodOrderRepository;
|
||||||
use App\State\OpenOrdersProvider;
|
use App\State\OpenFoodOrderProvider;
|
||||||
use App\State\LatestOrderProvider;
|
|
||||||
use DateInterval;
|
use DateInterval;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
@ -21,26 +16,12 @@ use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
use function iterator_to_array;
|
use function iterator_to_array;
|
||||||
|
|
||||||
#[ApiResource(
|
|
||||||
operations: [
|
|
||||||
new GetCollection(
|
|
||||||
uriTemplate: 'food_orders/open',
|
|
||||||
description: 'Get only open orders',
|
|
||||||
provider: OpenOrdersProvider::class,
|
|
||||||
),
|
|
||||||
new GetCollection(
|
|
||||||
uriTemplate: 'food_orders/latest',
|
|
||||||
description: 'Get the latest created order',
|
|
||||||
provider: LatestOrderProvider::class,
|
|
||||||
),
|
|
||||||
new GetCollection,
|
|
||||||
new Get,
|
|
||||||
new Post,
|
|
||||||
new Put,
|
|
||||||
new Delete,
|
|
||||||
]
|
|
||||||
)]
|
|
||||||
#[ORM\Entity(repositoryClass: FoodOrderRepository::class)]
|
#[ORM\Entity(repositoryClass: FoodOrderRepository::class)]
|
||||||
|
#[GetCollection(
|
||||||
|
uriTemplate: '/food_orders/open',
|
||||||
|
provider: OpenFoodOrderProvider::class,
|
||||||
|
)]
|
||||||
|
#[ApiResource]
|
||||||
class FoodOrder
|
class FoodOrder
|
||||||
{
|
{
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
|
|
|
@ -12,8 +12,8 @@ use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
||||||
use Symfony\Bridge\Doctrine\Types\UlidType;
|
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
#[ApiResource]
|
|
||||||
#[ORM\Entity(repositoryClass: MenuItemRepository::class)]
|
#[ORM\Entity(repositoryClass: MenuItemRepository::class)]
|
||||||
|
#[ApiResource]
|
||||||
class MenuItem
|
class MenuItem
|
||||||
{
|
{
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
|
|
|
@ -9,8 +9,8 @@ use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
||||||
use Symfony\Bridge\Doctrine\Types\UlidType;
|
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
#[ApiResource]
|
|
||||||
#[ORM\Entity(repositoryClass: OrderItemRepository::class)]
|
#[ORM\Entity(repositoryClass: OrderItemRepository::class)]
|
||||||
|
#[ApiResource]
|
||||||
class OrderItem
|
class OrderItem
|
||||||
{
|
{
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
|
|
|
@ -62,16 +62,4 @@ final class FoodOrderRepository extends ServiceEntityRepository
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getResult();
|
->getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return FoodOrder|null
|
|
||||||
*/
|
|
||||||
public function findLatestOrder(): ?FoodOrder
|
|
||||||
{
|
|
||||||
return $this->createQueryBuilder('alias')
|
|
||||||
->orderBy('alias.id', 'DESC')
|
|
||||||
->setMaxResults(1)
|
|
||||||
->getQuery()
|
|
||||||
->getOneOrNullResult();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\State;
|
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
|
||||||
use ApiPlatform\State\ProviderInterface;
|
|
||||||
use App\Entity\FoodOrder;
|
|
||||||
use App\Repository\FoodOrderRepository;
|
|
||||||
use Override;
|
|
||||||
|
|
||||||
final readonly class LatestOrderProvider implements ProviderInterface
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private FoodOrderRepository $repository
|
|
||||||
) {}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?FoodOrder
|
|
||||||
{
|
|
||||||
return $this->repository->findLatestOrder();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,21 +4,17 @@ namespace App\State;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
use ApiPlatform\Metadata\Operation;
|
||||||
use ApiPlatform\State\ProviderInterface;
|
use ApiPlatform\State\ProviderInterface;
|
||||||
use App\Entity\FoodOrder;
|
|
||||||
use App\Repository\FoodOrderRepository;
|
use App\Repository\FoodOrderRepository;
|
||||||
use Override;
|
use Override;
|
||||||
|
|
||||||
final readonly class OpenOrdersProvider implements ProviderInterface
|
final readonly class OpenFoodOrderProvider implements ProviderInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private FoodOrderRepository $repository
|
private FoodOrderRepository $repository
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return FoodOrder[]
|
|
||||||
*/
|
|
||||||
#[Override]
|
#[Override]
|
||||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
|
||||||
{
|
{
|
||||||
return $this->repository->findOpenOrders();
|
return $this->repository->findOpenOrders();
|
||||||
}
|
}
|
128
symfony.lock
128
symfony.lock
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"api-platform/core": {
|
"api-platform/symfony": {
|
||||||
"version": "4.1",
|
"version": "4.0",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "4.0",
|
"version": "4.0",
|
||||||
"ref": "cb9e6b8ceb9b62f32d41fc8ad72a25d5bd674c6d"
|
"ref": "e9952e9f393c2d048f10a78f272cd35e807d972b"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/api_platform.yaml",
|
"config/packages/api_platform.yaml",
|
||||||
|
@ -13,22 +13,13 @@
|
||||||
"src/ApiResource/.gitignore"
|
"src/ApiResource/.gitignore"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"doctrine/deprecations": {
|
|
||||||
"version": "1.1",
|
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "1.0",
|
|
||||||
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"doctrine/doctrine-bundle": {
|
"doctrine/doctrine-bundle": {
|
||||||
"version": "2.14",
|
"version": "2.12",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "2.13",
|
"version": "2.12",
|
||||||
"ref": "620b57f496f2e599a6015a9fa222c2ee0a32adcb"
|
"ref": "7266981c201efbbe02ae53c87f8bb378e3f825ae"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/doctrine.yaml",
|
"config/packages/doctrine.yaml",
|
||||||
|
@ -37,7 +28,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"doctrine/doctrine-fixtures-bundle": {
|
"doctrine/doctrine-fixtures-bundle": {
|
||||||
"version": "4.1",
|
"version": "4.0",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -49,7 +40,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"doctrine/doctrine-migrations-bundle": {
|
"doctrine/doctrine-migrations-bundle": {
|
||||||
"version": "3.4",
|
"version": "3.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -62,7 +53,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"liip/test-fixtures-bundle": {
|
"liip/test-fixtures-bundle": {
|
||||||
"version": "3.4.0"
|
"version": "3.2.1"
|
||||||
},
|
},
|
||||||
"nelmio/cors-bundle": {
|
"nelmio/cors-bundle": {
|
||||||
"version": "2.5",
|
"version": "2.5",
|
||||||
|
@ -77,46 +68,39 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"phpstan/phpstan": {
|
"phpstan/phpstan": {
|
||||||
"version": "1.12",
|
"version": "1.11",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes-contrib",
|
"repo": "github.com/symfony/recipes-contrib",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
|
"ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
|
||||||
},
|
}
|
||||||
"files": [
|
|
||||||
"phpstan.dist.neon"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"phpunit/phpunit": {
|
"phpunit/phpunit": {
|
||||||
"version": "11.5",
|
"version": "11.4",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "11.1",
|
"version": "9.6",
|
||||||
"ref": "c6658a60fc9d594805370eacdf542c3d6b5c0869"
|
"ref": "7364a21d87e658eb363c5020c072ecfdc12e2326"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
".env.test",
|
".env.test",
|
||||||
"phpunit.xml.dist",
|
"phpunit.xml.dist",
|
||||||
"tests/bootstrap.php",
|
"tests/bootstrap.php"
|
||||||
"bin/phpunit"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"squizlabs/php_codesniffer": {
|
"squizlabs/php_codesniffer": {
|
||||||
"version": "3.13",
|
"version": "3.10",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes-contrib",
|
"repo": "github.com/symfony/recipes-contrib",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "3.6",
|
"version": "3.6",
|
||||||
"ref": "1019e5c08d4821cb9b77f4891f8e9c31ff20ac6f"
|
"ref": "1019e5c08d4821cb9b77f4891f8e9c31ff20ac6f"
|
||||||
},
|
}
|
||||||
"files": [
|
|
||||||
"phpcs.xml.dist"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"symfony/console": {
|
"symfony/console": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -128,37 +112,24 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/flex": {
|
"symfony/flex": {
|
||||||
"version": "2.7",
|
"version": "2.4",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "2.4",
|
"version": "1.0",
|
||||||
"ref": "52e9754527a15e2b79d9a610f98185a1fe46622a"
|
"ref": "146251ae39e06a95be0fe3d13c807bcf3938b172"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
".env",
|
".env"
|
||||||
".env.dev"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"symfony/form": {
|
|
||||||
"version": "7.3",
|
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "7.2",
|
|
||||||
"ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"config/packages/csrf.yaml"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/framework-bundle": {
|
"symfony/framework-bundle": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "7.3",
|
"version": "7.0",
|
||||||
"ref": "5a1497d539f691b96afd45ae397ce5fe30beb4b9"
|
"ref": "6356c19b9ae08e7763e4ba2d9ae63043efc75db5"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/cache.yaml",
|
"config/packages/cache.yaml",
|
||||||
|
@ -168,12 +139,11 @@
|
||||||
"config/services.yaml",
|
"config/services.yaml",
|
||||||
"public/index.php",
|
"public/index.php",
|
||||||
"src/Controller/.gitignore",
|
"src/Controller/.gitignore",
|
||||||
"src/Kernel.php",
|
"src/Kernel.php"
|
||||||
".editorconfig"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/maker-bundle": {
|
"symfony/maker-bundle": {
|
||||||
"version": "1.63",
|
"version": "1.60",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -181,20 +151,8 @@
|
||||||
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"symfony/property-info": {
|
|
||||||
"version": "7.3",
|
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "7.3",
|
|
||||||
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"config/packages/property_info.yaml"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"symfony/routing": {
|
"symfony/routing": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -207,7 +165,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/security-bundle": {
|
"symfony/security-bundle": {
|
||||||
"version": "7.3",
|
"version": "7.2",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -220,7 +178,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/twig-bundle": {
|
"symfony/twig-bundle": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -233,7 +191,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/uid": {
|
"symfony/uid": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -242,7 +200,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"symfony/validator": {
|
"symfony/validator": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -254,16 +212,32 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/web-profiler-bundle": {
|
"symfony/web-profiler-bundle": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "7.3",
|
"version": "6.1",
|
||||||
"ref": "5b2b543e13942495c0003f67780cb4448af9e606"
|
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/web_profiler.yaml",
|
"config/packages/web_profiler.yaml",
|
||||||
"config/routes/web_profiler.yaml"
|
"config/routes/web_profiler.yaml"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"symfony/webpack-encore-bundle": {
|
||||||
|
"version": "2.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "2.0",
|
||||||
|
"ref": "9ef5412a4a2a8415aca3a3f2b4edd3866aab9a19"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"assets/app.js",
|
||||||
|
"assets/styles/app.css",
|
||||||
|
"config/packages/webpack_encore.yaml",
|
||||||
|
"package.json",
|
||||||
|
"webpack.config.js"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,44 +2,59 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="color-scheme" content="dark light">
|
<meta name="color-scheme" content="dark light">
|
||||||
<meta name="theme-color" content="#0000ff" media="(prefers-color-scheme: light)">
|
<meta name="theme-color" content="#0000ff" media="(prefers-color-scheme: light)">
|
||||||
<meta name="theme-color" content="#222222" media="(prefers-color-scheme: dark)">
|
<meta name="theme-color" content="#222222" media="(prefers-color-scheme: dark)">
|
||||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||||
<link rel="icon" type="image/svg+xml"
|
<link rel="icon" type="image/svg+xml"
|
||||||
href="{{ favicon }}" />
|
href="{{ favicon }}" />
|
||||||
{% set currentDate = "now"|date("d") %}
|
{# Use Encore assets for CSS and JS #}
|
||||||
{% if currentDate % 4 == 0 %}
|
{% block stylesheets %}
|
||||||
<link rel="stylesheet" href="/static/css/new.min.css">
|
{{ encore_entry_link_tags('app') }}
|
||||||
{% elseif currentDate % 4 == 1 %}
|
{% endblock %}
|
||||||
<link rel="stylesheet" href="/static/css/simple.min.css">
|
|
||||||
{% elseif currentDate % 4 == 2 %}
|
{% block javascripts %}
|
||||||
<link rel="stylesheet" href="/static/css/water.min.css">
|
{{ encore_entry_script_tags('app') }}
|
||||||
{% else %}
|
{% endblock %}
|
||||||
<link rel="stylesheet" href="/static/css/fieber.css">
|
|
||||||
{% endif %}
|
|
||||||
<style>
|
|
||||||
label{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src="/static/js/htmx.min.js"></script>
|
<script src="/static/js/htmx.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<p>Hello {{ app.request.cookies.get('username', 'nobody') }} - <a href="{{ path('username') }}">change name</a></p>
|
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||||
<nav>
|
<div class="container">
|
||||||
<a href="{{ path('app_food_order_index') }}">Orders</a> /
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<a href="{{ path('app_food_vendor_index') }}">Vendors</a> /
|
<span class="navbar-toggler-icon"></span>
|
||||||
<a
|
</button>
|
||||||
href="https://git.hannover.ccc.de/lubiana/futtern/issues/new"
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
target="_blank"
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
>Create Issue</a> /
|
<li class="nav-item">
|
||||||
<a href="/api">API</a>
|
<a class="nav-link" href="{{ path('app_food_order_index') }}">Orders</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ path('app_food_vendor_index') }}">Vendors</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="https://git.hannover.ccc.de/lubiana/futtern/issues/new" target="_blank">Create Issue</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/api" target="_blank">API</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="d-flex">
|
||||||
|
<span class="navbar-text me-2">
|
||||||
|
Hello {{ app.request.cookies.get('username', 'nobody') }}
|
||||||
|
<a href="{{ path('username') }}">Change name</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main class="py-4">
|
||||||
{% block body %}{% endblock %}
|
<div class="container">
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
hx-get="{{ path('app_food_order_new') }}"
|
hx-get="{{ path('app_food_order_new') }}"
|
||||||
hx-trigger="click"
|
hx-trigger="click"
|
||||||
hx-target="closest div"
|
hx-target="closest div"
|
||||||
|
class="btn btn-primary"
|
||||||
>Create new</button>
|
>Create new</button>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
|
@ -1,2 +1,10 @@
|
||||||
{{ include('_form.html.twig') }}
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
{{ include('_form.html.twig') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="{{ path('app_food_order_index') }}" class="btn btn-secondary">
|
||||||
|
<i class="bi bi-arrow-left"></i> Back to list
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
|
@ -3,30 +3,32 @@
|
||||||
{% block title %}New OrderItem{% endblock %}
|
{% block title %}New OrderItem{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>Create new OrderItem</h1>
|
<h1 class="mb-4">Create new OrderItem</h1>
|
||||||
|
|
||||||
{{ include('order_item/_form.html.twig') }}
|
{{ include('order_item/_form.html.twig') }}
|
||||||
|
|
||||||
<hr />
|
<hr class="my-4" />
|
||||||
|
|
||||||
{% if food_order.foodVendor.menuLink != '' %}
|
{% if food_order.foodVendor.menuLink != '' %}
|
||||||
<a href="{{ food_order.foodVendor.menuLink }}" target="_blank">
|
<a href="{{ food_order.foodVendor.menuLink }}" class="btn btn-info mb-3" target="_blank">
|
||||||
External link to Menu
|
<i class="bi bi-link-45deg"></i> External link to Menu
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div>
|
<div class="mb-3">
|
||||||
<b>click a button to select a given menuitem</b>
|
<b>Click a button to select a given menu item:</b>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="mb-4">
|
||||||
{% for menuItem in menuItems %}
|
{% for menuItem in menuItems %}
|
||||||
<a href="#" data-menu-item>{{ menuItem.name }}</a>
|
<a href="#" class="btn btn-outline-secondary me-2 mb-2" data-menu-item>{{ menuItem.name }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr class="my-4" />
|
||||||
|
|
||||||
<a class="button" href="{{ path('app_food_order_show', { 'id': food_order.id}) }}">back to list</a>
|
<a class="btn btn-secondary" href="{{ path('app_food_order_show', { 'id': food_order.id}) }}">
|
||||||
|
<i class="bi bi-arrow-left"></i> Back to list
|
||||||
|
</a>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.querySelectorAll('[data-menu-item]').forEach(function(element) {
|
document.querySelectorAll('[data-menu-item]').forEach(function(element) {
|
||||||
|
|
26
tests/DbApiTestCase.php
Normal file
26
tests/DbApiTestCase.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests;
|
||||||
|
|
||||||
|
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
|
||||||
|
use ApiPlatform\Symfony\Bundle\Test\Client;
|
||||||
|
use App\DataFixtures\AppFixtures;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Liip\TestFixturesBundle\Services\DatabaseToolCollection;
|
||||||
|
use Override;
|
||||||
|
|
||||||
|
abstract class DbApiTestCase extends ApiTestCase
|
||||||
|
{
|
||||||
|
protected EntityManagerInterface $manager;
|
||||||
|
protected Client $client;
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->client = static::createClient();
|
||||||
|
$this->manager = static::getContainer()->get('doctrine')->getManager();
|
||||||
|
$toolKit = self::getContainer()->get(DatabaseToolCollection::class)->get();
|
||||||
|
$toolKit->loadFixtures([AppFixtures::class]);
|
||||||
|
}
|
||||||
|
}
|
48
tests/Feature/Api/ApiSmokeTest.php
Normal file
48
tests/Feature/Api/ApiSmokeTest.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
test('orders', function (): void {
|
||||||
|
$response = $this->client->request('GET', '/api/food_orders');
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
|
||||||
|
$this->assertJsonContains([
|
||||||
|
'@context' => '/api/contexts/FoodOrder',
|
||||||
|
'@id' => '/api/food_orders',
|
||||||
|
'@type' => 'Collection',
|
||||||
|
'totalItems' => 1,
|
||||||
|
]);
|
||||||
|
$array = $response->toArray();
|
||||||
|
expect($array['member'][0]['orderItems'])->toHaveCount(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('open orders', function (): void {
|
||||||
|
$response = $this->client->request('GET', '/api/food_orders/open');
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
|
||||||
|
$this->assertJsonContains([
|
||||||
|
'@context' => '/api/contexts/FoodOrder',
|
||||||
|
'@id' => '/api/food_orders/open',
|
||||||
|
'@type' => 'Collection',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Get the response content and verify that all returned orders are open
|
||||||
|
$array = $response->toArray();
|
||||||
|
|
||||||
|
// If we have no items, we should still have a successful response
|
||||||
|
if ($array['totalItems'] === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each order in the response, check that it is not closed
|
||||||
|
foreach ($array['member'] as $order) {
|
||||||
|
// An order is considered open if either:
|
||||||
|
// 1. closedAt is null or
|
||||||
|
// 2. closedAt is in the future
|
||||||
|
$closedAt = isset($order['closedAt']) ? new DateTimeImmutable($order['closedAt']) : null;
|
||||||
|
$now = new DateTimeImmutable;
|
||||||
|
|
||||||
|
// Assert that the order is open (not closed)
|
||||||
|
$isOpen = (! $closedAt instanceof DateTimeImmutable || $closedAt > $now);
|
||||||
|
expect($isOpen)
|
||||||
|
->toBeTrue('Order should be open but is closed');
|
||||||
|
}
|
||||||
|
});
|
|
@ -231,30 +231,6 @@ describe(FoodOrderController::class, function (): void {
|
||||||
$this->assertTrue($openOrder->isClosed());
|
$this->assertTrue($openOrder->isClosed());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('api_latest_order', function (): void {
|
|
||||||
// Create an older order
|
|
||||||
$olderOrder = new FoodOrder($this->generateOldUlid());
|
|
||||||
$olderOrder->setFoodVendor($this->vendor);
|
|
||||||
$this->manager->persist($olderOrder);
|
|
||||||
|
|
||||||
// Create the latest order
|
|
||||||
$latestOrder = new FoodOrder;
|
|
||||||
$latestOrder->setFoodVendor($this->vendor);
|
|
||||||
$this->manager->persist($latestOrder);
|
|
||||||
|
|
||||||
$this->manager->flush();
|
|
||||||
|
|
||||||
// Test the API endpoint
|
|
||||||
$this->client->request('GET', '/api/food_orders/latest');
|
|
||||||
$this->assertResponseIsSuccessful();
|
|
||||||
$this->assertResponseHeaderSame('Content-Type', 'application/ld+json; charset=utf-8');
|
|
||||||
|
|
||||||
$response = json_decode($this->client->getResponse()->getContent(), true);
|
|
||||||
$this->assertIsArray($response);
|
|
||||||
$this->assertArrayHasKey('@id', $response);
|
|
||||||
$this->assertStringContainsString($latestOrder->getId()->__toString(), $response['@id']);
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
})
|
||||||
->covers(
|
->covers(
|
||||||
FoodOrderController::class,
|
FoodOrderController::class,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Tests\DbApiTestCase;
|
||||||
use App\Tests\DbWebTest;
|
use App\Tests\DbWebTest;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -15,6 +16,8 @@ use App\Tests\DbWebTest;
|
||||||
|
|
||||||
pest()
|
pest()
|
||||||
->extends(DbWebTest::class)->in('Feature/Controller/*.php');
|
->extends(DbWebTest::class)->in('Feature/Controller/*.php');
|
||||||
|
pest()
|
||||||
|
->extends(DbApiTestCase::class)->in('Feature/Api/*.php');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
20
tests/Unit/Service/FaviconTest.php
Normal file
20
tests/Unit/Service/FaviconTest.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Unit\Service;
|
||||||
|
|
||||||
|
use App\Service\Favicon;
|
||||||
|
|
||||||
|
use function expect;
|
||||||
|
use function test;
|
||||||
|
|
||||||
|
test('Favicon returns SVG string', function (): void {
|
||||||
|
$favicon = new Favicon();
|
||||||
|
$result = (string)$favicon;
|
||||||
|
|
||||||
|
expect($result)
|
||||||
|
->toBeString()
|
||||||
|
->toContain('data:image/svg+xml')
|
||||||
|
->toContain('transform=\'rotate(')
|
||||||
|
->toContain('viewBox=\'0 0 512 512\'');
|
||||||
|
})
|
||||||
|
->covers(Favicon::class);
|
84
webpack.config.js
Normal file
84
webpack.config.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
const Encore = require('@symfony/webpack-encore');
|
||||||
|
|
||||||
|
// Manually configure the runtime environment if not already configured yet by the "encore" command.
|
||||||
|
// It's useful when you use tools that rely on webpack.config.js file.
|
||||||
|
if (!Encore.isRuntimeEnvironmentConfigured()) {
|
||||||
|
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
|
||||||
|
}
|
||||||
|
|
||||||
|
Encore
|
||||||
|
// directory where compiled assets will be stored
|
||||||
|
.setOutputPath('public/build/')
|
||||||
|
// public path used by the web server to access the output path
|
||||||
|
.setPublicPath('/build')
|
||||||
|
|
||||||
|
// only needed for CDN's or subdirectory deploy
|
||||||
|
//.setManifestKeyPrefix('build/')
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ENTRY CONFIG
|
||||||
|
*
|
||||||
|
* Each entry will result in one JavaScript file (e.g. app.js)
|
||||||
|
* and one CSS file (e.g. app.css) if your JavaScript imports CSS.
|
||||||
|
*/
|
||||||
|
.addEntry('app', './assets/app.js')
|
||||||
|
|
||||||
|
// enables the Symfony UX Stimulus bridge (used in assets/bootstrap.js)
|
||||||
|
//.enableStimulusBridge('./assets/controllers.json')
|
||||||
|
|
||||||
|
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
|
||||||
|
.splitEntryChunks()
|
||||||
|
|
||||||
|
// will require an extra script tag for runtime.js
|
||||||
|
// but, you probably want this, unless you're building a single-page app
|
||||||
|
.enableSingleRuntimeChunk()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FEATURE CONFIG
|
||||||
|
*
|
||||||
|
* Enable & configure other features below. For a full
|
||||||
|
* list of features, see:
|
||||||
|
* https://symfony.com/doc/current/frontend.html#adding-more-features
|
||||||
|
*/
|
||||||
|
.cleanupOutputBeforeBuild()
|
||||||
|
.enableBuildNotifications()
|
||||||
|
.enableSourceMaps(!Encore.isProduction())
|
||||||
|
// enables hashed filenames (e.g. app.abc123.css)
|
||||||
|
.enableVersioning(Encore.isProduction())
|
||||||
|
|
||||||
|
// configure Babel
|
||||||
|
// .configureBabel((config) => {
|
||||||
|
// config.plugins.push('@babel/plugin-proposal-class-properties');
|
||||||
|
// })
|
||||||
|
|
||||||
|
// enables and configure @babel/preset-env polyfills
|
||||||
|
.configureBabelPresetEnv((config) => {
|
||||||
|
config.useBuiltIns = 'usage';
|
||||||
|
config.corejs = '3.36';
|
||||||
|
})
|
||||||
|
|
||||||
|
// enables Sass/SCSS support
|
||||||
|
.enableSassLoader()
|
||||||
|
|
||||||
|
// uncomment if you use TypeScript
|
||||||
|
//.enableTypeScriptLoader()
|
||||||
|
|
||||||
|
// uncomment to get integrity="..." attributes on your script & link tags
|
||||||
|
// requires WebpackEncoreBundle 1.4 or higher
|
||||||
|
//.enableIntegrityHashes(Encore.isProduction())
|
||||||
|
|
||||||
|
// Enable CSS and JS minification in production
|
||||||
|
.enablePostCssLoader((options) => {
|
||||||
|
options.postcssOptions = {
|
||||||
|
plugins: [
|
||||||
|
require('autoprefixer')
|
||||||
|
]
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
// When in production, minify CSS and JS automatically
|
||||||
|
// uncomment if you're having problems with a jQuery plugin
|
||||||
|
//.autoProvidejQuery()
|
||||||
|
;
|
||||||
|
|
||||||
|
module.exports = Encore.getWebpackConfig();
|
Loading…
Add table
Add a link
Reference in a new issue