forked from lubiana/futtern
Compare commits
16 commits
tailiwindi
...
main
Author | SHA1 | Date | |
---|---|---|---|
4d2ae3a6ab | |||
98feafa3fc | |||
c8e6af6896 | |||
e2167fa19f | |||
c29e09ccc0 | |||
a5e54e9f5b | |||
9b1e3d98f0 | |||
5cb66c5012 | |||
c99032044d | |||
6bb49e8f79 | |||
300c8cafc9 | |||
ee32852789 | |||
9d2f0204e3 | |||
a948e992d8 | |||
f731b46f86 | |||
937973e8e9 |
77 changed files with 3107 additions and 3656 deletions
4
.env.dev
Normal file
4
.env.dev
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
APP_SECRET=11c8937d48993fb3aee1a476413161f5
|
||||||
|
###< symfony/framework-bundle ###
|
|
@ -3,7 +3,7 @@ jobs:
|
||||||
ls:
|
ls:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: git.php.fail/lubiana/container/php:8.4.3-ci
|
image: git.php.fail/lubiana/container/php:8.4.8-ci
|
||||||
steps:
|
steps:
|
||||||
- name: Manually checkout
|
- name: Manually checkout
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -6,7 +6,7 @@ jobs:
|
||||||
ls:
|
ls:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: git.php.fail/lubiana/container/php:8.4.3-ci
|
image: git.php.fail/lubiana/container/php:8.4.8-ci
|
||||||
steps:
|
steps:
|
||||||
- name: Manually checkout
|
- name: Manually checkout
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -4,7 +4,7 @@ jobs:
|
||||||
ls:
|
ls:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: git.php.fail/lubiana/container/php:8.4.3-ci
|
image: git.php.fail/lubiana/container/php:8.4.8-ci
|
||||||
steps:
|
steps:
|
||||||
- name: Manually checkout
|
- name: Manually checkout
|
||||||
env:
|
env:
|
||||||
|
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -18,6 +18,15 @@
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
###> squizlabs/php_codesniffer ###
|
||||||
|
/.phpcs-cache
|
||||||
|
/phpcs.xml
|
||||||
|
###< squizlabs/php_codesniffer ###
|
||||||
|
|
||||||
|
###> phpstan/phpstan ###
|
||||||
|
phpstan.neon
|
||||||
|
###< phpstan/phpstan ###
|
||||||
|
|
||||||
###> symfony/asset-mapper ###
|
###> symfony/asset-mapper ###
|
||||||
/public/assets/
|
/public/assets/
|
||||||
/assets/vendor/
|
/assets/vendor/
|
||||||
|
|
|
@ -1,156 +0,0 @@
|
||||||
# Development Guidelines
|
|
||||||
|
|
||||||
This document provides guidelines and instructions for developing and maintaining the Futtern project.
|
|
||||||
|
|
||||||
## Build/Configuration Instructions
|
|
||||||
|
|
||||||
### Environment Setup
|
|
||||||
|
|
||||||
1. **PHP Requirements**: The project requires PHP 8.4 or higher with the following extensions:
|
|
||||||
- ctype
|
|
||||||
- iconv
|
|
||||||
|
|
||||||
2. **Composer**: Install dependencies using Composer:
|
|
||||||
```bash
|
|
||||||
composer install
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Environment Configuration**: Copy `.env` to `.env.local` and adjust the settings as needed for your local environment.
|
|
||||||
|
|
||||||
### Development Server
|
|
||||||
|
|
||||||
Start the Symfony development server:
|
|
||||||
```bash
|
|
||||||
symfony server:start
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building Assets
|
|
||||||
|
|
||||||
The project uses Tailwind CSS via the symfonycasts/tailwind-bundle:
|
|
||||||
```bash
|
|
||||||
# Install importmap assets
|
|
||||||
symfony console importmap:install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deployment
|
|
||||||
|
|
||||||
The project includes several deployment scripts:
|
|
||||||
|
|
||||||
1. **Prepare for Deployment**:
|
|
||||||
```bash
|
|
||||||
./deploy/prepare-deploy.sh
|
|
||||||
```
|
|
||||||
This script creates a clean copy of the application with only production dependencies.
|
|
||||||
|
|
||||||
2. **Local Deployment**:
|
|
||||||
```bash
|
|
||||||
./deploy/local-deploy.sh
|
|
||||||
```
|
|
||||||
This script deploys the application to a remote server, backing up the database and restarting the service.
|
|
||||||
|
|
||||||
3. **Update After Deployment**:
|
|
||||||
```bash
|
|
||||||
./deploy/update.sh
|
|
||||||
```
|
|
||||||
This script is run on the remote server to clear cache, warm up cache, and run database migrations.
|
|
||||||
|
|
||||||
## Testing Information
|
|
||||||
|
|
||||||
### Testing Framework
|
|
||||||
|
|
||||||
The project uses Pest PHP, a testing framework built on top of PHPUnit, for testing. Tests are organized into:
|
|
||||||
- **Feature Tests**: For testing controllers and API endpoints
|
|
||||||
- **Unit Tests**: For testing individual components
|
|
||||||
|
|
||||||
### Running Tests
|
|
||||||
|
|
||||||
Run all tests:
|
|
||||||
```bash
|
|
||||||
composer test
|
|
||||||
# or
|
|
||||||
./vendor/bin/pest
|
|
||||||
```
|
|
||||||
|
|
||||||
Run specific tests:
|
|
||||||
```bash
|
|
||||||
./vendor/bin/pest tests/Unit/ExampleTest.php
|
|
||||||
```
|
|
||||||
|
|
||||||
Run tests in parallel:
|
|
||||||
```bash
|
|
||||||
./vendor/bin/pest --parallel
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating Tests
|
|
||||||
|
|
||||||
1. **Unit Tests**: Create files in the `tests/Unit` directory.
|
|
||||||
2. **Feature Tests**:
|
|
||||||
- Controller tests: Create files in `tests/Feature/Controller`
|
|
||||||
- API tests: Create files in `tests/Feature/Api`
|
|
||||||
|
|
||||||
### Example Test
|
|
||||||
|
|
||||||
Here's a simple example of a Pest PHP test:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
test('example test', function (): void {
|
|
||||||
expect(true)->toBeTrue();
|
|
||||||
expect(1 + 1)->toBe(2);
|
|
||||||
expect('hello world')->toContain('world');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('array operations', function (): void {
|
|
||||||
$array = [1, 2, 3];
|
|
||||||
|
|
||||||
expect($array)->toHaveCount(3);
|
|
||||||
expect($array)->toContain(2);
|
|
||||||
expect($array[0])->toBe(1);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Base Classes
|
|
||||||
|
|
||||||
- `DbWebTest`: Base class for controller tests
|
|
||||||
- `DbApiTestCase`: Base class for API tests
|
|
||||||
|
|
||||||
## Code Quality and Style
|
|
||||||
|
|
||||||
### Code Style
|
|
||||||
|
|
||||||
The project uses Easy Coding Standard (ECS) for code style checking:
|
|
||||||
```bash
|
|
||||||
composer lint
|
|
||||||
# or
|
|
||||||
./vendor/bin/ecs --fix
|
|
||||||
```
|
|
||||||
|
|
||||||
The code style is defined in `ecs.php` and includes:
|
|
||||||
- Custom rules from `Lubiana\CodeQuality\LubiSetList::ECS`
|
|
||||||
- All classes should be declared as final
|
|
||||||
|
|
||||||
### Code Refactoring
|
|
||||||
|
|
||||||
The project uses Rector for automated code refactoring:
|
|
||||||
```bash
|
|
||||||
./vendor/bin/rector
|
|
||||||
```
|
|
||||||
|
|
||||||
The refactoring rules are defined in `rector.php` and include:
|
|
||||||
- Custom rules from `Lubiana\CodeQuality\LubiSetList::RECTOR`
|
|
||||||
- StaticClosureRector is skipped in the tests directory
|
|
||||||
|
|
||||||
### Development Workflow
|
|
||||||
|
|
||||||
1. Make changes to the code
|
|
||||||
2. Run tests to ensure functionality: `composer test`
|
|
||||||
3. Fix code style issues: `composer lint`
|
|
||||||
4. Commit and push changes
|
|
||||||
5. Deploy using the deployment scripts
|
|
||||||
|
|
||||||
## Debugging
|
|
||||||
|
|
||||||
- Use the Symfony Web Profiler in development mode
|
|
||||||
- Check logs in `var/log/`
|
|
||||||
- For API debugging, use the API Platform documentation at `/api`
|
|
|
@ -1,3 +0,0 @@
|
||||||
workers:
|
|
||||||
tailwind:
|
|
||||||
cmd: ['symfony', 'console', 'tailwind:build', '--watch']
|
|
|
@ -4,5 +4,21 @@
|
||||||
* This file will be included onto the page via the importmap() Twig function,
|
* This file will be included onto the page via the importmap() Twig function,
|
||||||
* which should already be in your base.html.twig.
|
* which should already be in your base.html.twig.
|
||||||
*/
|
*/
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
import './styles/app.css';
|
import './styles/app.css';
|
||||||
|
import './styles/modes.css';
|
||||||
|
import './styles/emoji-footprint.css';
|
||||||
|
|
||||||
|
// Import modules
|
||||||
|
import './javascript/theme.js';
|
||||||
|
import './javascript/emoji-footprint.js';
|
||||||
|
import './javascript/modes.js';
|
||||||
|
import './javascript/htmx.js';
|
||||||
|
import emojiButtonListener from './javascript/emoji-button.js';
|
||||||
|
import 'bootstrap';
|
||||||
|
import { initRadioState } from './javascript/radioState.js';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initRadioState();
|
||||||
|
emojiButtonListener();
|
||||||
|
});
|
14
assets/javascript/emoji-button.js
Normal file
14
assets/javascript/emoji-button.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
const emojiButtonListener = function () {
|
||||||
|
const buttons = document.querySelectorAll('.emoji-buttons .btn.btn-primary');
|
||||||
|
|
||||||
|
buttons.forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const emojiField = document.querySelector('#food_vendor_emojis');
|
||||||
|
if (emojiField) {
|
||||||
|
emojiField.value += this.textContent;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emojiButtonListener;
|
19
assets/javascript/emoji-footprint.js
Normal file
19
assets/javascript/emoji-footprint.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// Sparkle effect on mouse move
|
||||||
|
document.addEventListener('mousemove', function (e) {
|
||||||
|
const emojis = ['✨', '💖', '🌟', '💅', '🦄', '🎉', '🌈'];
|
||||||
|
const sparkle = document.createElement('div');
|
||||||
|
sparkle.className = 'emoji-footprint';
|
||||||
|
sparkle.textContent = emojis[Math.floor(Math.random() * emojis.length)];
|
||||||
|
sparkle.style.left = e.pageX + 'px';
|
||||||
|
sparkle.style.top = e.pageY + 'px';
|
||||||
|
document.body.appendChild(sparkle);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
sparkle.remove();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
export function initEmojiFootprint() {
|
||||||
|
// The sparkle effect is already initialized when this module is imported
|
||||||
|
// This function can be used if we need to control when the effect starts
|
||||||
|
}
|
3
assets/javascript/htmx.js
Normal file
3
assets/javascript/htmx.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import htmx from 'htmx.org';
|
||||||
|
|
||||||
|
window.htmx = htmx;
|
136
assets/javascript/modes.js
Normal file
136
assets/javascript/modes.js
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
// Bonkers mode functionality
|
||||||
|
function setEmojiLevelClass(mode) {
|
||||||
|
document.body.classList.remove('emoji-normal', 'emoji-enhanced', 'emoji-bonkers');
|
||||||
|
if (mode === 'bonkers') {
|
||||||
|
document.body.classList.add('emoji-bonkers');
|
||||||
|
} else if (mode === 'enhanced') {
|
||||||
|
document.body.classList.add('emoji-enhanced');
|
||||||
|
} else {
|
||||||
|
document.body.classList.add('emoji-normal');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initBonkersMode() {
|
||||||
|
// Check if we're in bonkers mode
|
||||||
|
const currentMode = document.documentElement.getAttribute('data-website-mode');
|
||||||
|
setEmojiLevelClass(currentMode);
|
||||||
|
|
||||||
|
if (currentMode === 'bonkers') {
|
||||||
|
// Apply bonkers mode immediately
|
||||||
|
document.body.classList.add('bonkers-mode');
|
||||||
|
|
||||||
|
// Start the fabulous effects
|
||||||
|
createExtraSparkles();
|
||||||
|
createSlayEffects();
|
||||||
|
|
||||||
|
console.log('🌈✨ Bonkers mode activated! ✨🌈');
|
||||||
|
} else {
|
||||||
|
// Remove bonkers mode if it was active
|
||||||
|
document.body.classList.remove('bonkers-mode');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to create extra sparkles during bonkers mode
|
||||||
|
function createExtraSparkles() {
|
||||||
|
const currentMode = document.documentElement.getAttribute('data-website-mode');
|
||||||
|
if (currentMode !== 'bonkers') return;
|
||||||
|
|
||||||
|
const extraEmojis = [
|
||||||
|
'💃', '🕺',
|
||||||
|
'🍑', '💦', '😏', '😈', '👅', '💋', '🥵', '😳', '🤤', '😍', '🥴',
|
||||||
|
'💕', '💖', '💗', '💘', '💝', '💞', '💟', '💌', '💏', '💑',
|
||||||
|
'🍆', '🥒', '🍌', '💦', '👀', '😉', '😌', '😍', '🥰', '😘',
|
||||||
|
'😚', '😋', '😏', '😫', '😩', '🥺', '🥵', '🥴',
|
||||||
|
'💖', '💗', '💕', '💞', '💓', '💗', '💖', '💘', '💝',
|
||||||
|
'💋', '💏', '💑'
|
||||||
|
];
|
||||||
|
const sparkle = document.createElement('div');
|
||||||
|
sparkle.className = 'emoji-footprint';
|
||||||
|
sparkle.textContent = extraEmojis[Math.floor(Math.random() * extraEmojis.length)];
|
||||||
|
sparkle.style.left = Math.random() * window.innerWidth + 'px';
|
||||||
|
sparkle.style.top = Math.random() * window.innerHeight + 'px';
|
||||||
|
document.body.appendChild(sparkle);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (sparkle.parentNode) {
|
||||||
|
sparkle.remove();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
// Continue creating extra sparkles while in bonkers mode
|
||||||
|
const newMode = document.documentElement.getAttribute('data-website-mode');
|
||||||
|
if (newMode === 'bonkers') {
|
||||||
|
setTimeout(() => createExtraSparkles(), 150);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to create slay effects
|
||||||
|
function createSlayEffects() {
|
||||||
|
const currentMode = document.documentElement.getAttribute('data-website-mode');
|
||||||
|
if (currentMode !== 'bonkers') return;
|
||||||
|
|
||||||
|
// Create floating "SLAY" text effects
|
||||||
|
const slayWords = [
|
||||||
|
'SLAY', 'QUEEN', 'FABULOUS', 'ICONIC', 'LEGENDARY', 'STUNNING', 'GORGEOUS', 'FLAWLESS',
|
||||||
|
'DAZZLING', 'RADIANT', 'BREATHTAKING', 'EXQUISITE', 'DIVINE'
|
||||||
|
];
|
||||||
|
const slayElement = document.createElement('div');
|
||||||
|
slayElement.className = 'slay-text';
|
||||||
|
slayElement.textContent = slayWords[Math.floor(Math.random() * slayWords.length)];
|
||||||
|
slayElement.style.left = Math.random() * window.innerWidth + 'px';
|
||||||
|
slayElement.style.top = Math.random() * window.innerHeight + 'px';
|
||||||
|
document.body.appendChild(slayElement);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (slayElement.parentNode) {
|
||||||
|
slayElement.remove();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
// Continue creating slay effects while in bonkers mode
|
||||||
|
const newMode = document.documentElement.getAttribute('data-website-mode');
|
||||||
|
if (newMode === 'bonkers') {
|
||||||
|
setTimeout(() => createSlayEffects(), 800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for mode changes
|
||||||
|
function watchModeChanges() {
|
||||||
|
// Create a MutationObserver to watch for changes to the data-website-mode attribute
|
||||||
|
const observer = new MutationObserver(function(mutations) {
|
||||||
|
mutations.forEach(function(mutation) {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName === 'data-website-mode') {
|
||||||
|
const newMode = document.documentElement.getAttribute('data-website-mode');
|
||||||
|
|
||||||
|
if (newMode === 'bonkers') {
|
||||||
|
document.body.classList.add('bonkers-mode');
|
||||||
|
setEmojiLevelClass(newMode);
|
||||||
|
|
||||||
|
// Start the fabulous effects
|
||||||
|
createExtraSparkles();
|
||||||
|
createSlayEffects();
|
||||||
|
|
||||||
|
console.log('🌈✨ Switched to bonkers mode! ✨🌈');
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('bonkers-mode');
|
||||||
|
setEmojiLevelClass(newMode);
|
||||||
|
console.log(`😴 Switched to ${newMode} mode`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start observing
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['data-website-mode']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize when DOM is loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
initBonkersMode();
|
||||||
|
watchModeChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
export { initBonkersMode, watchModeChanges };
|
55
assets/javascript/numberInputs.js
Normal file
55
assets/javascript/numberInputs.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// Function to initialize number input buttons
|
||||||
|
function initNumberInputs(container = document) {
|
||||||
|
container.querySelectorAll('.number-input-wrapper').forEach(function(wrapper) {
|
||||||
|
const input = wrapper.querySelector('input[type="number"]');
|
||||||
|
const decreaseBtn = wrapper.querySelector('[data-action="decrease"]');
|
||||||
|
const increaseBtn = wrapper.querySelector('[data-action="increase"]');
|
||||||
|
|
||||||
|
if (!input || !decreaseBtn || !increaseBtn) return;
|
||||||
|
|
||||||
|
// Skip if already initialized
|
||||||
|
if (decreaseBtn.hasAttribute('data-initialized')) return;
|
||||||
|
|
||||||
|
const step = parseFloat(input.getAttribute('step')) || 1;
|
||||||
|
const min = 0;
|
||||||
|
const max = input.getAttribute('max') ? parseFloat(input.getAttribute('max')) : null;
|
||||||
|
|
||||||
|
decreaseBtn.addEventListener('click', function() {
|
||||||
|
const currentValue = parseFloat(input.value) || 0;
|
||||||
|
const newValue = currentValue - step;
|
||||||
|
|
||||||
|
if (min === null || newValue >= min) {
|
||||||
|
input.value = newValue;
|
||||||
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
increaseBtn.addEventListener('click', function() {
|
||||||
|
const currentValue = parseFloat(input.value) || 0;
|
||||||
|
const newValue = currentValue + step;
|
||||||
|
|
||||||
|
if (max === null || newValue <= max) {
|
||||||
|
input.value = newValue;
|
||||||
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validate input on change
|
||||||
|
input.addEventListener('input', function() {
|
||||||
|
const value = parseFloat(this.value);
|
||||||
|
|
||||||
|
if (min !== null && value < min) {
|
||||||
|
this.value = min;
|
||||||
|
}
|
||||||
|
if (max !== null && value > max) {
|
||||||
|
this.value = max;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mark as initialized
|
||||||
|
decreaseBtn.setAttribute('data-initialized', 'true');
|
||||||
|
increaseBtn.setAttribute('data-initialized', 'true');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { initNumberInputs };
|
35
assets/javascript/radioState.js
Normal file
35
assets/javascript/radioState.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Radio button state management with localStorage
|
||||||
|
function initRadioState() {
|
||||||
|
// Store and retrieve radio button state
|
||||||
|
const radioButtons = document.querySelectorAll('input[name="mode"]');
|
||||||
|
|
||||||
|
// Load saved state on page load
|
||||||
|
const savedMode = localStorage.getItem('selectedMode');
|
||||||
|
if (savedMode) {
|
||||||
|
const radioToCheck = document.getElementById(savedMode);
|
||||||
|
if (radioToCheck) {
|
||||||
|
radioToCheck.checked = true;
|
||||||
|
// Set the data attribute to match the saved mode
|
||||||
|
document.documentElement.setAttribute('data-website-mode', savedMode);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no saved state, set to the currently checked radio button
|
||||||
|
const checkedRadio = document.querySelector('input[name="mode"]:checked');
|
||||||
|
if (checkedRadio) {
|
||||||
|
document.documentElement.setAttribute('data-website-mode', checkedRadio.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save state when radio button changes
|
||||||
|
radioButtons.forEach(radio => {
|
||||||
|
radio.addEventListener('change', function() {
|
||||||
|
if (this.checked) {
|
||||||
|
localStorage.setItem('selectedMode', this.id);
|
||||||
|
// Update the data attribute when mode changes
|
||||||
|
document.documentElement.setAttribute('data-website-mode', this.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { initRadioState };
|
18
assets/javascript/theme.js
Normal file
18
assets/javascript/theme.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Theme detection and switching
|
||||||
|
const getPreferredTheme = () => {
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTheme = theme => {
|
||||||
|
document.documentElement.setAttribute('data-bs-theme', theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial theme
|
||||||
|
setTheme(getPreferredTheme())
|
||||||
|
|
||||||
|
// Listen for changes in user's preferred color scheme
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||||
|
setTheme(getPreferredTheme())
|
||||||
|
})
|
||||||
|
|
||||||
|
export { getPreferredTheme, setTheme };
|
|
@ -1,7 +1,179 @@
|
||||||
@tailwind base;
|
/*
|
||||||
@tailwind components;
|
* =================================================================================================
|
||||||
@tailwind utilities;
|
* 💖 BUBBLEGUM PUNK THEME (LIGHT) 💖
|
||||||
|
*
|
||||||
|
* This isn't just a theme. It's a statement.
|
||||||
|
* Unapologetically loud, pink, and quirky.
|
||||||
|
* =================================================================================================
|
||||||
|
*/
|
||||||
|
:root,
|
||||||
|
[data-bs-theme=light] {
|
||||||
|
/* --- CORE VIBE --- */
|
||||||
|
--bs-pink: #FF007A; /* 💖 Hyper Pink (Our Queen) */
|
||||||
|
--bs-green: #CFFF50; /* 🧪 Toxic Slime */
|
||||||
|
--bs-purple: #A328D6; /* 👾 Graffiti Purple */
|
||||||
|
--bs-yellow: #F9F871; /* ⚡ Neon Lemon */
|
||||||
|
--bs-cyan: #00F5D4; /* 💎 Glitchy Teal */
|
||||||
|
--bs-blue: #00A9E0; /* 💦 Splash Zone */
|
||||||
|
|
||||||
body {
|
/* Let's redefine ALL the core colors to match the new energy */
|
||||||
background-color: skyblue;
|
--bs-primary: var(--bs-pink);
|
||||||
}
|
--bs-secondary: var(--bs-green);
|
||||||
|
--bs-success: var(--bs-cyan);
|
||||||
|
--bs-info: var(--bs-blue);
|
||||||
|
--bs-warning: var(--bs-yellow);
|
||||||
|
--bs-danger: #FF3D3D; /* 🚨 Code Red Rave */
|
||||||
|
|
||||||
|
/* --- BACKGROUNDS & TEXT --- */
|
||||||
|
/* No more boring white! */
|
||||||
|
--bs-body-bg: #FFF5FD; /* A soft, dreamy pink canvas */
|
||||||
|
--bs-body-color: #4A003D; /* Dark Plum (instead of black) for text */
|
||||||
|
--bs-heading-color: var(--bs-purple); /* Make headings POP */
|
||||||
|
--bs-secondary-color: rgba(74, 0, 61, 0.75); /* Plum, but softer */
|
||||||
|
--bs-tertiary-color: rgba(74, 0, 61, 0.5);
|
||||||
|
|
||||||
|
/* Make cards and containers pure white to contrast the pink background */
|
||||||
|
--bs-tertiary-bg: #FFFFFF;
|
||||||
|
--bs-secondary-bg: #FEF9FE;
|
||||||
|
|
||||||
|
/* --- LINKS & CODE --- */
|
||||||
|
--bs-link-color: var(--bs-pink);
|
||||||
|
--bs-link-hover-color: var(--bs-purple);
|
||||||
|
--bs-code-color: var(--bs-purple);
|
||||||
|
|
||||||
|
/* --- BORDERS & SHADOWS: LET'S GET QUIRKY --- */
|
||||||
|
--bs-border-width: 2px; /* Chunky borders! */
|
||||||
|
--bs-border-color: #FFD6F5; /* Pink-tinted border color */
|
||||||
|
--bs-border-color-translucent: rgba(74, 0, 61, 0.2);
|
||||||
|
--bs-border-radius: 1rem; /* Super bubbly and round */
|
||||||
|
--bs-border-radius-sm: 0.5rem;
|
||||||
|
--bs-border-radius-lg: 1.5rem;
|
||||||
|
--bs-border-radius-pill: 50rem;
|
||||||
|
|
||||||
|
/* Say goodbye to black shadows, hello to colored glows! */
|
||||||
|
--bs-box-shadow: 0 4px 12px rgba(255, 0, 122, 0.2);
|
||||||
|
--bs-box-shadow-sm: 0 2px 4px rgba(255, 0, 122, 0.15);
|
||||||
|
--bs-box-shadow-lg: 0 8px 30px rgba(255, 0, 122, 0.25);
|
||||||
|
--bs-box-shadow-inset: inset 0 1px 4px rgba(74, 0, 61, 0.2);
|
||||||
|
|
||||||
|
/* --- THE GRADIENT: THE SOUL OF THE THEME --- */
|
||||||
|
--bs-gradient: linear-gradient(75deg, var(--bs-primary), var(--bs-secondary));
|
||||||
|
|
||||||
|
/* --- Don't forget the RGB values for Bootstrap components! --- */
|
||||||
|
--bs-primary-rgb: 255, 0, 122;
|
||||||
|
--bs-secondary-rgb: 207, 255, 80;
|
||||||
|
--bs-body-color-rgb: 74, 0, 61;
|
||||||
|
--bs-body-bg-rgb: 255, 245, 253;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =================================================================================================
|
||||||
|
* 🌙🦇 CYBER GOTH THEME (DARK) 🦇🌙
|
||||||
|
*
|
||||||
|
* The lights are out, the neon is ON.
|
||||||
|
* A dark, moody theme with vibrant, glowing accents.
|
||||||
|
* =================================================================================================
|
||||||
|
*/
|
||||||
|
[data-bs-theme=dark] {
|
||||||
|
color-scheme: dark;
|
||||||
|
|
||||||
|
/* --- BACKGROUNDS & TEXT --- */
|
||||||
|
--bs-body-bg: #1D001A; /* Deep, dark space purple */
|
||||||
|
--bs-body-color: #FFE9FA; /* Light pink text for high contrast */
|
||||||
|
--bs-heading-color: var(--bs-cyan); /* Glowing cyan headings */
|
||||||
|
|
||||||
|
--bs-tertiary-bg: #2E0028; /* A slightly lighter container background */
|
||||||
|
--bs-secondary-bg: #3A0033;
|
||||||
|
--bs-secondary-color: rgba(255, 233, 250, 0.75);
|
||||||
|
--bs-tertiary-color: rgba(255, 233, 250, 0.5);
|
||||||
|
|
||||||
|
/* --- LINKS & CODE --- */
|
||||||
|
/* Using the Toxic Slime for links gives it that cyber look */
|
||||||
|
--bs-link-color: var(--bs-green);
|
||||||
|
--bs-link-hover-color: var(--bs-cyan);
|
||||||
|
--bs-code-color: var(--bs-pink);
|
||||||
|
|
||||||
|
/* --- BORDERS & SHADOWS: NEON GLOWS --- */
|
||||||
|
--bs-border-color: #5C004F;
|
||||||
|
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||||
|
|
||||||
|
/* Redefine shadows to be neon glows */
|
||||||
|
--bs-box-shadow: 0 0 15px rgba(var(--bs-primary-rgb), 0.4);
|
||||||
|
--bs-box-shadow-lg: 0 0 30px rgba(var(--bs-primary-rgb), 0.5);
|
||||||
|
|
||||||
|
/* --- EMPHASIS & SUBTLE BACKGROUNDS --- */
|
||||||
|
/* These are for alerts, badges, etc. They'll be dark with glowing text. */
|
||||||
|
--bs-primary-text-emphasis: #FF8AD1;
|
||||||
|
--bs-secondary-text-emphasis: #E2FF8A;
|
||||||
|
--bs-success-text-emphasis: #8AFFEB;
|
||||||
|
--bs-info-text-emphasis: #7ADCF5;
|
||||||
|
--bs-warning-text-emphasis: #FAF8A8;
|
||||||
|
--bs-danger-text-emphasis: #FF8A8A;
|
||||||
|
|
||||||
|
--bs-primary-bg-subtle: #3D002B;
|
||||||
|
--bs-secondary-bg-subtle: #415215;
|
||||||
|
--bs-success-bg-subtle: #00332B;
|
||||||
|
--bs-info-bg-subtle: #00313D;
|
||||||
|
--bs-warning-bg-subtle: #3E3D1C;
|
||||||
|
--bs-danger-bg-subtle: #520E0E;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === EMOJI LEVELS === */
|
||||||
|
.emoji-normal .emoji-normal { display: inline; }
|
||||||
|
.emoji-normal .emoji-enhanced,
|
||||||
|
.emoji-normal .emoji-bonkers { display: none; }
|
||||||
|
|
||||||
|
.emoji-enhanced .emoji-enhanced { display: inline; }
|
||||||
|
.emoji-enhanced .emoji-normal,
|
||||||
|
.emoji-enhanced .emoji-bonkers { display: none; }
|
||||||
|
|
||||||
|
.emoji-bonkers .emoji-bonkers { display: inline; }
|
||||||
|
.emoji-bonkers .emoji-normal,
|
||||||
|
.emoji-bonkers .emoji-enhanced { display: none; }
|
||||||
|
/*
|
||||||
|
* =================================================================================================
|
||||||
|
* 🌈 RAINBOW PRIDE ELEMENTS 🌈
|
||||||
|
*
|
||||||
|
* Fabulous rainbow-themed elements to celebrate diversity and pride!
|
||||||
|
* =================================================================================================
|
||||||
|
*/
|
||||||
|
.bg-rainbow {
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
#FF5757, /* Red */
|
||||||
|
#FFBD59, /* Orange */
|
||||||
|
#F9F871, /* Yellow */
|
||||||
|
#CFFF50, /* Green */
|
||||||
|
#00F5D4, /* Teal */
|
||||||
|
#00A9E0, /* Blue */
|
||||||
|
#A328D6 /* Purple */
|
||||||
|
);
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||||
|
font-weight: bold;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fun-fact {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a subtle rainbow border to the fun facts card */
|
||||||
|
.card:has(.fun-fact) {
|
||||||
|
border-width: 2px;
|
||||||
|
border-style: solid;
|
||||||
|
border-image: linear-gradient(
|
||||||
|
to right,
|
||||||
|
#FF5757, /* Red */
|
||||||
|
#FFBD59, /* Orange */
|
||||||
|
#F9F871, /* Yellow */
|
||||||
|
#CFFF50, /* Green */
|
||||||
|
#00F5D4, /* Teal */
|
||||||
|
#00A9E0, /* Blue */
|
||||||
|
#A328D6 /* Purple */
|
||||||
|
) 1;
|
||||||
|
box-shadow: 0 4px 15px rgba(163, 40, 214, 0.2);
|
||||||
|
}
|
||||||
|
|
30
assets/styles/emoji-footprint.css
Normal file
30
assets/styles/emoji-footprint.css
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
/* Emoji Footprint Animation */
|
||||||
|
.emoji-footprint {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: emojiFade 1s ease-out forwards;
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 9999;
|
||||||
|
text-shadow:
|
||||||
|
0 0 4px #ff00bf,
|
||||||
|
0 0 8px #ff80df,
|
||||||
|
0 0 12px #ffccff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes emojiFade {
|
||||||
|
0% {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(-50%, -50%) scale(1.5);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-50%, -50%) scale(2);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
568
assets/styles/modes.css
Normal file
568
assets/styles/modes.css
Normal file
|
@ -0,0 +1,568 @@
|
||||||
|
/* 🌈✨ BONKERS MODE ANIMATIONS ✨🌈 */
|
||||||
|
@keyframes rainbowGradient {
|
||||||
|
0% { background-position: 0% 50%; }
|
||||||
|
50% { background-position: 100% 50%; }
|
||||||
|
100% { background-position: 0% 50%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes discoFlash {
|
||||||
|
0%, 100% {
|
||||||
|
background-color: var(--bs-pink);
|
||||||
|
box-shadow: 0 0 20px var(--bs-pink), 0 0 40px var(--bs-pink);
|
||||||
|
}
|
||||||
|
16.66% {
|
||||||
|
background-color: var(--bs-purple);
|
||||||
|
box-shadow: 0 0 20px var(--bs-purple), 0 0 40px var(--bs-purple);
|
||||||
|
}
|
||||||
|
33.33% {
|
||||||
|
background-color: var(--bs-cyan);
|
||||||
|
box-shadow: 0 0 20px var(--bs-cyan), 0 0 40px var(--bs-cyan);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-color: var(--bs-yellow);
|
||||||
|
box-shadow: 0 0 20px var(--bs-yellow), 0 0 40px var(--bs-yellow);
|
||||||
|
}
|
||||||
|
66.66% {
|
||||||
|
background-color: var(--bs-green);
|
||||||
|
box-shadow: 0 0 20px var(--bs-green), 0 0 40px var(--bs-green);
|
||||||
|
}
|
||||||
|
83.33% {
|
||||||
|
background-color: var(--bs-orange);
|
||||||
|
box-shadow: 0 0 20px var(--bs-orange), 0 0 40px var(--bs-orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wiggle {
|
||||||
|
0%, 100% { transform: rotate(0deg); }
|
||||||
|
25% { transform: rotate(-2deg); }
|
||||||
|
75% { transform: rotate(2deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.05); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rainbowText {
|
||||||
|
0% { color: var(--bs-red); }
|
||||||
|
14.28% { color: var(--bs-orange); }
|
||||||
|
28.57% { color: var(--bs-yellow); }
|
||||||
|
42.85% { color: var(--bs-green); }
|
||||||
|
57.14% { color: var(--bs-cyan); }
|
||||||
|
71.42% { color: var(--bs-purple); }
|
||||||
|
85.71% { color: var(--bs-pink); }
|
||||||
|
100% { color: var(--bs-red); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shine {
|
||||||
|
0% { left: -100%; }
|
||||||
|
50% { left: 100%; }
|
||||||
|
100% { left: 100%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slayFloat {
|
||||||
|
0% {
|
||||||
|
transform: translateY(0) scale(0.5);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
transform: translateY(-20px) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: translateY(-60px) scale(1.2);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(-100px) scale(1.5);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 🎭 BONKERS MODE CLASSES 🎭 */
|
||||||
|
.bonkers-mode {
|
||||||
|
background: linear-gradient(270deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red), var(--bs-pink));
|
||||||
|
background-size: 1600% 1600%;
|
||||||
|
animation: rainbowGradient 10s ease infinite;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .btn {
|
||||||
|
animation: discoFlash 0.3s infinite, wiggle 0.2s infinite;
|
||||||
|
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red));
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: discoFlash 0.3s infinite, wiggle 0.2s infinite, rainbowGradient 1s ease infinite;
|
||||||
|
border: 4px solid var(--bs-white);
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .btn:hover {
|
||||||
|
animation: discoFlash 0.2s infinite, wiggle 0.1s infinite, rainbowGradient 0.5s ease infinite;
|
||||||
|
box-shadow: 0 0 30px var(--bs-pink), 0 0 60px var(--bs-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: linear-gradient(45deg, transparent, rgba(255,255,255,0.5), transparent);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
animation: spin 0.5s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar {
|
||||||
|
background: linear-gradient(90deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red));
|
||||||
|
background-size: 200% 200%;
|
||||||
|
animation: rainbowGradient 2s ease infinite;
|
||||||
|
box-shadow: 0 0 50px rgba(255, 105, 180, 0.9);
|
||||||
|
height: auto !important;
|
||||||
|
min-height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar-brand {
|
||||||
|
animation: rainbowText 0.8s infinite, wiggle 0.4s infinite;
|
||||||
|
font-size: 1.8em;
|
||||||
|
text-shadow: 3px 3px 6px rgba(0,0,0,0.5);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar-brand::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: linear-gradient(45deg, transparent, rgba(255,255,255,0.3), transparent);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar-nav .nav-link {
|
||||||
|
animation: rainbowText 1.2s infinite, wiggle 0.3s infinite;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin: 0 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar-nav .nav-link::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
|
||||||
|
animation: shine 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar-nav .nav-link:hover {
|
||||||
|
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
||||||
|
border-color: var(--bs-white);
|
||||||
|
box-shadow: 0 0 20px var(--bs-pink);
|
||||||
|
animation: discoFlash 0.5s infinite, wiggle 0.2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar-nav .nav-link.active {
|
||||||
|
background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange));
|
||||||
|
border-color: var(--bs-white);
|
||||||
|
box-shadow: 0 0 25px var(--bs-yellow);
|
||||||
|
animation: discoFlash 0.8s infinite, wiggle 0.3s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar-text {
|
||||||
|
animation: rainbowText 1.5s infinite, wiggle 0.5s infinite;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
||||||
|
border: 2px solid var(--bs-white);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: linear-gradient(45deg, var(--bs-cyan), var(--bs-blue));
|
||||||
|
box-shadow: 0 0 15px var(--bs-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar-toggler {
|
||||||
|
border: 3px solid var(--bs-white);
|
||||||
|
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
||||||
|
animation: discoFlash 0.6s infinite, wiggle 0.4s infinite;
|
||||||
|
box-shadow: 0 0 20px var(--bs-pink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar-toggler:focus {
|
||||||
|
box-shadow: 0 0 30px var(--bs-pink), 0 0 0 0.2rem rgba(255, 105, 180, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar-toggler-icon {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .dropdown-menu {
|
||||||
|
background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan));
|
||||||
|
border: 3px solid var(--bs-white);
|
||||||
|
box-shadow: 0 0 30px rgba(255,105,180,0.8);
|
||||||
|
animation: rainbowGradient 2s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .dropdown-item {
|
||||||
|
animation: rainbowText 1.8s infinite, wiggle 0.6s infinite;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .dropdown-item:hover {
|
||||||
|
background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange));
|
||||||
|
color: var(--bs-white);
|
||||||
|
box-shadow: 0 0 15px var(--bs-yellow);
|
||||||
|
animation: discoFlash 0.5s infinite, wiggle 0.3s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .navbar-collapse {
|
||||||
|
background: linear-gradient(135deg, rgba(255,105,180,0.1), rgba(138,43,226,0.1));
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 2px solid var(--bs-pink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode h1, .bonkers-mode h2, .bonkers-mode h3 {
|
||||||
|
animation: rainbowText 1.5s infinite;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .table {
|
||||||
|
background: linear-gradient(135deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2), rgba(0,255,255,0.2));
|
||||||
|
animation: rainbowGradient 3s ease infinite;
|
||||||
|
border: 3px solid var(--bs-pink);
|
||||||
|
box-shadow: 0 0 30px rgba(255,105,180,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .table th {
|
||||||
|
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
||||||
|
color: var(--bs-white);
|
||||||
|
animation: discoFlash 0.8s infinite;
|
||||||
|
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .form-control {
|
||||||
|
border: 3px solid var(--bs-pink);
|
||||||
|
box-shadow: 0 0 15px var(--bs-pink);
|
||||||
|
animation: pulse 0.6s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .alert {
|
||||||
|
animation: discoFlash 0.6s infinite, wiggle 0.3s infinite;
|
||||||
|
border: 4px solid var(--bs-white);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .card {
|
||||||
|
background: linear-gradient(45deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2));
|
||||||
|
border: 3px solid var(--bs-purple);
|
||||||
|
box-shadow: 0 0 35px rgba(138,43,226,0.6);
|
||||||
|
animation: pulse 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .modal-content {
|
||||||
|
background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan));
|
||||||
|
border: 4px solid var(--bs-white);
|
||||||
|
box-shadow: 0 0 50px rgba(255,105,180,0.8);
|
||||||
|
animation: rainbowGradient 2s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .modal-header {
|
||||||
|
background: linear-gradient(90deg, var(--bs-yellow), var(--bs-orange));
|
||||||
|
animation: discoFlash 0.8s infinite;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .number-input-wrapper {
|
||||||
|
animation: wiggle 0.4s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonkers-mode .number-input-wrapper .btn {
|
||||||
|
animation: discoFlash 0.3s infinite, wiggle 0.2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced mode styles (for future use) */
|
||||||
|
[data-website-mode="enhanced"] .btn {
|
||||||
|
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red));
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: rainbowGradient 1s ease infinite;
|
||||||
|
border: 4px solid var(--bs-white);
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .btn:hover {
|
||||||
|
animation: rainbowGradient 0.5s ease infinite;
|
||||||
|
box-shadow: 0 0 30px var(--bs-pink), 0 0 60px var(--bs-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: linear-gradient(45deg, transparent, rgba(255,255,255,0.5), transparent);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
animation: spin 0.5s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar {
|
||||||
|
background: linear-gradient(90deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red));
|
||||||
|
background-size: 200% 200%;
|
||||||
|
animation: rainbowGradient 2s ease infinite;
|
||||||
|
box-shadow: 0 0 50px rgba(255, 105, 180, 0.9);
|
||||||
|
height: auto !important;
|
||||||
|
min-height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar-brand {
|
||||||
|
animation: rainbowText 0.8s infinite;
|
||||||
|
font-size: 1.8em;
|
||||||
|
text-shadow: 3px 3px 6px rgba(0,0,0,0.5);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar-brand::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: linear-gradient(45deg, transparent, rgba(255,255,255,0.3), transparent);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar-nav .nav-link {
|
||||||
|
animation: rainbowText 1.2s infinite;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin: 0 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar-nav .nav-link::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
|
||||||
|
animation: shine 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar-nav .nav-link:hover {
|
||||||
|
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
||||||
|
border-color: var(--bs-white);
|
||||||
|
box-shadow: 0 0 20px var(--bs-pink);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar-nav .nav-link.active {
|
||||||
|
background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange));
|
||||||
|
border-color: var(--bs-white);
|
||||||
|
box-shadow: 0 0 25px var(--bs-yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar-text {
|
||||||
|
animation: rainbowText 1.5s infinite;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
||||||
|
border: 2px solid var(--bs-white);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: linear-gradient(45deg, var(--bs-cyan), var(--bs-blue));
|
||||||
|
box-shadow: 0 0 15px var(--bs-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar-toggler {
|
||||||
|
border: 3px solid var(--bs-white);
|
||||||
|
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
||||||
|
animation: rainbowGradient 0.6s ease infinite;
|
||||||
|
box-shadow: 0 0 20px var(--bs-pink);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar-toggler:focus {
|
||||||
|
box-shadow: 0 0 30px var(--bs-pink), 0 0 0 0.2rem rgba(255, 105, 180, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar-toggler-icon {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .dropdown-menu {
|
||||||
|
background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan));
|
||||||
|
border: 3px solid var(--bs-white);
|
||||||
|
box-shadow: 0 0 30px rgba(255,105,180,0.8);
|
||||||
|
animation: rainbowGradient 2s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .dropdown-item {
|
||||||
|
animation: rainbowText 1.8s infinite;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .dropdown-item:hover {
|
||||||
|
background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange));
|
||||||
|
color: var(--bs-white);
|
||||||
|
box-shadow: 0 0 15px var(--bs-yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .navbar-collapse {
|
||||||
|
background: linear-gradient(135deg, rgba(255,105,180,0.1), rgba(138,43,226,0.1));
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 2px solid var(--bs-pink);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] h1, [data-website-mode="enhanced"] h2, [data-website-mode="enhanced"] h3 {
|
||||||
|
animation: rainbowText 1.5s infinite;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .table {
|
||||||
|
background: linear-gradient(135deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2), rgba(0,255,255,0.2));
|
||||||
|
animation: rainbowGradient 3s ease infinite;
|
||||||
|
border: 3px solid var(--bs-pink);
|
||||||
|
box-shadow: 0 0 30px rgba(255,105,180,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .table th {
|
||||||
|
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
||||||
|
color: var(--bs-white);
|
||||||
|
animation: rainbowGradient 0.8s ease infinite;
|
||||||
|
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .form-control {
|
||||||
|
border: 3px solid var(--bs-pink);
|
||||||
|
box-shadow: 0 0 15px var(--bs-pink);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .alert {
|
||||||
|
animation: rainbowGradient 0.6s ease infinite;
|
||||||
|
border: 4px solid var(--bs-white);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .card {
|
||||||
|
background: linear-gradient(45deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2));
|
||||||
|
border: 3px solid var(--bs-purple);
|
||||||
|
box-shadow: 0 0 35px rgba(138,43,226,0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .modal-content {
|
||||||
|
background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan));
|
||||||
|
border: 4px solid var(--bs-white);
|
||||||
|
box-shadow: 0 0 50px rgba(255,105,180,0.8);
|
||||||
|
animation: rainbowGradient 2s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .modal-header {
|
||||||
|
background: linear-gradient(90deg, var(--bs-yellow), var(--bs-orange));
|
||||||
|
animation: rainbowGradient 0.8s ease infinite;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .number-input-wrapper {
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-website-mode="enhanced"] .number-input-wrapper .btn {
|
||||||
|
animation: rainbowGradient 0.3s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Emoji Footprint Animation */
|
||||||
|
.emoji-footprint {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: emojiFade 1s ease-out forwards;
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 9999;
|
||||||
|
text-shadow:
|
||||||
|
0 0 4px #ff00bf,
|
||||||
|
0 0 8px #ff80df,
|
||||||
|
0 0 12px #ffccff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes emojiFade {
|
||||||
|
0% {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(-50%, -50%) scale(1.5);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-50%, -50%) scale(2);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 💅 SLAY TEXT EFFECTS 💅 */
|
||||||
|
.slay-text {
|
||||||
|
position: fixed;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10000;
|
||||||
|
animation: slayFloat 3s ease-out forwards;
|
||||||
|
text-shadow:
|
||||||
|
0 0 10px #ff00bf,
|
||||||
|
0 0 20px #ff80df,
|
||||||
|
0 0 30px #ffccff,
|
||||||
|
2px 2px 4px rgba(0,0,0,0.5);
|
||||||
|
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow));
|
||||||
|
background-size: 400% 400%;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
animation: slayFloat 3s ease-out forwards, rainbowGradient 1s ease infinite;
|
||||||
|
}
|
|
@ -7,47 +7,50 @@
|
||||||
"php": ">=8.4",
|
"php": ">=8.4",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"api-platform/doctrine-orm": "^4.0.0",
|
"api-platform/doctrine-orm": "*",
|
||||||
"api-platform/symfony": "4.1.4",
|
"api-platform/symfony": "*",
|
||||||
"doctrine/dbal": "^4.1",
|
"doctrine/dbal": "^4.2.3",
|
||||||
"doctrine/doctrine-bundle": "^2.12",
|
"doctrine/doctrine-bundle": "^2.14.1",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.3.1",
|
"doctrine/doctrine-migrations-bundle": "^3.4.2",
|
||||||
"doctrine/orm": "^3.2.1",
|
"doctrine/orm": "^3.4.0",
|
||||||
"nelmio/cors-bundle": "^2.5",
|
"nelmio/cors-bundle": "^2.5",
|
||||||
"phpdocumentor/reflection-docblock": "^5.6",
|
"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.2.*",
|
"symfony/asset": "7.3.*",
|
||||||
"symfony/console": "7.1.*",
|
"symfony/asset-mapper": "7.3.*",
|
||||||
"symfony/dotenv": "7.1.*",
|
"symfony/console": "7.3.*",
|
||||||
"symfony/expression-language": "7.2.*",
|
"symfony/dotenv": "7.3.*",
|
||||||
"symfony/flex": "^2.4.6",
|
"symfony/expression-language": "7.3.*",
|
||||||
"symfony/form": "7.1.*",
|
"symfony/flex": "^2.7.1",
|
||||||
"symfony/framework-bundle": "7.1.*",
|
"symfony/form": "7.3.*",
|
||||||
"symfony/property-access": "7.2.*",
|
"symfony/framework-bundle": "7.3.*",
|
||||||
"symfony/property-info": "7.2.*",
|
"symfony/monolog-bundle": "^3.10",
|
||||||
"symfony/runtime": "7.1.*",
|
"symfony/property-access": "7.3.*",
|
||||||
"symfony/security-bundle": "7.2.*",
|
"symfony/property-info": "7.3.*",
|
||||||
"symfony/security-csrf": "7.1.*",
|
"symfony/runtime": "7.3.*",
|
||||||
"symfony/serializer": "7.2.*",
|
"symfony/security-bundle": "7.3.*",
|
||||||
"symfony/twig-bundle": "7.1.*",
|
"symfony/security-csrf": "7.3.*",
|
||||||
"symfony/uid": "7.1.*",
|
"symfony/serializer": "7.3.*",
|
||||||
"symfony/validator": "7.1.*",
|
"symfony/twig-bundle": "7.3.*",
|
||||||
"symfony/yaml": "7.1.*",
|
"symfony/uid": "7.3.*",
|
||||||
"symfonycasts/tailwind-bundle": "^0.10.0"
|
"symfony/validator": "7.3.*",
|
||||||
|
"symfony/yaml": "7.3.*",
|
||||||
|
"twig/extra-bundle": "^2.12|^3.0",
|
||||||
|
"twig/twig": "^2.12|^3.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/doctrine-fixtures-bundle": "^4.0",
|
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
||||||
"liip/test-fixtures-bundle": "^3.2",
|
"liip/test-fixtures-bundle": "^3.4",
|
||||||
"lubiana/code-quality": "^1.7.2",
|
"lubiana/code-quality": "^1.7.2",
|
||||||
"pestphp/pest": "^3.6",
|
"pestphp/pest": "^3.8.2",
|
||||||
"symfony/browser-kit": "7.2.*",
|
"symfony/browser-kit": "7.3.*",
|
||||||
"symfony/css-selector": "7.2.*",
|
"symfony/css-selector": "7.3.*",
|
||||||
"symfony/http-client": "7.2.*",
|
"symfony/http-client": "7.3.*",
|
||||||
"symfony/maker-bundle": "^1.60",
|
"symfony/maker-bundle": "^1.63",
|
||||||
"symfony/stopwatch": "7.2.*",
|
"symfony/stopwatch": "7.3.*",
|
||||||
"symfony/web-profiler-bundle": "7.2.*",
|
"symfony/web-profiler-bundle": "7.3.*",
|
||||||
"symplify/config-transformer": "^12.3.4"
|
"symplify/config-transformer": "^12.4.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
|
@ -81,7 +84,8 @@
|
||||||
"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": {
|
||||||
|
@ -108,7 +112,7 @@
|
||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"allow-contrib": false,
|
"allow-contrib": false,
|
||||||
"require": "7.2.*"
|
"require": "7.3.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2977
composer.lock
generated
2977
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -8,10 +8,11 @@ use Liip\TestFixturesBundle\LiipTestFixturesBundle;
|
||||||
use Nelmio\CorsBundle\NelmioCorsBundle;
|
use Nelmio\CorsBundle\NelmioCorsBundle;
|
||||||
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||||
use Symfony\Bundle\MakerBundle\MakerBundle;
|
use Symfony\Bundle\MakerBundle\MakerBundle;
|
||||||
|
use Symfony\Bundle\MonologBundle\MonologBundle;
|
||||||
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 Symfonycasts\TailwindBundle\SymfonycastsTailwindBundle;
|
use Twig\Extra\TwigExtraBundle\TwigExtraBundle;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
FrameworkBundle::class => [
|
FrameworkBundle::class => [
|
||||||
|
@ -43,14 +44,17 @@ 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,
|
||||||
],
|
],
|
||||||
SymfonycastsTailwindBundle::class => [
|
ApiPlatformBundle::class => [
|
||||||
|
'all' => true,
|
||||||
|
],
|
||||||
|
TwigExtraBundle::class => [
|
||||||
|
'all' => true,
|
||||||
|
],
|
||||||
|
MonologBundle::class => [
|
||||||
'all' => true,
|
'all' => true,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -4,10 +4,8 @@ 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' => 'Futtern API',
|
'title' => 'Hello API Platform',
|
||||||
'version' => '1.0.0',
|
'version' => '1.0.0',
|
||||||
'show_webby' => false,
|
|
||||||
'enable_swagger' => true,
|
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'stateless' => true,
|
'stateless' => true,
|
||||||
'cache_headers' => [
|
'cache_headers' => [
|
||||||
|
|
20
config/packages/csrf.php
Normal file
20
config/packages/csrf.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?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',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
};
|
95
config/packages/monolog.php
Normal file
95
config/packages/monolog.php
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||||
|
|
||||||
|
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||||
|
$containerConfigurator->extension('monolog', [
|
||||||
|
'channels' => [
|
||||||
|
'deprecation',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
if ($containerConfigurator->env() === 'dev') {
|
||||||
|
$containerConfigurator->extension('monolog', [
|
||||||
|
'handlers' => [
|
||||||
|
'main' => [
|
||||||
|
'type' => 'stream',
|
||||||
|
'path' => '%kernel.logs_dir%/%kernel.environment%.log',
|
||||||
|
'level' => 'debug',
|
||||||
|
'channels' => [
|
||||||
|
'!event',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'console' => [
|
||||||
|
'type' => 'console',
|
||||||
|
'process_psr_3_messages' => false,
|
||||||
|
'channels' => [
|
||||||
|
'!event',
|
||||||
|
'!doctrine',
|
||||||
|
'!console',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($containerConfigurator->env() === 'test') {
|
||||||
|
$containerConfigurator->extension('monolog', [
|
||||||
|
'handlers' => [
|
||||||
|
'main' => [
|
||||||
|
'type' => 'fingers_crossed',
|
||||||
|
'action_level' => 'error',
|
||||||
|
'handler' => 'nested',
|
||||||
|
'excluded_http_codes' => [
|
||||||
|
404,
|
||||||
|
405,
|
||||||
|
],
|
||||||
|
'channels' => [
|
||||||
|
'!event',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'nested' => [
|
||||||
|
'type' => 'stream',
|
||||||
|
'path' => '%kernel.logs_dir%/%kernel.environment%.log',
|
||||||
|
'level' => 'debug',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($containerConfigurator->env() === 'prod') {
|
||||||
|
$containerConfigurator->extension('monolog', [
|
||||||
|
'handlers' => [
|
||||||
|
'main' => [
|
||||||
|
'type' => 'fingers_crossed',
|
||||||
|
'action_level' => 'error',
|
||||||
|
'handler' => 'nested',
|
||||||
|
'excluded_http_codes' => [
|
||||||
|
404,
|
||||||
|
405,
|
||||||
|
],
|
||||||
|
'buffer_size' => 50,
|
||||||
|
],
|
||||||
|
'nested' => [
|
||||||
|
'type' => 'stream',
|
||||||
|
'path' => 'php://stderr',
|
||||||
|
'level' => 'debug',
|
||||||
|
'formatter' => 'monolog.formatter.json',
|
||||||
|
],
|
||||||
|
'console' => [
|
||||||
|
'type' => 'console',
|
||||||
|
'process_psr_3_messages' => false,
|
||||||
|
'channels' => [
|
||||||
|
'!event',
|
||||||
|
'!doctrine',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'deprecation' => [
|
||||||
|
'type' => 'stream',
|
||||||
|
'channels' => [
|
||||||
|
'deprecation',
|
||||||
|
],
|
||||||
|
'path' => 'php://stderr',
|
||||||
|
'formatter' => 'monolog.formatter.json',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
};
|
|
@ -3,7 +3,9 @@
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||||
$containerConfigurator->extension('symfonycasts_tailwind', [
|
$containerConfigurator->extension('framework', [
|
||||||
'binary_version' => 'v3.4.17',
|
'property_info' => [
|
||||||
|
'with_constructor_extractor' => true,
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
};
|
};
|
|
@ -1,17 +1,16 @@
|
||||||
<?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 (
|
||||||
$containerConfigurator->extension('twig', [
|
ContainerConfigurator $containerConfigurator,
|
||||||
'file_name_pattern' => '*.twig',
|
TwigConfig $twig,
|
||||||
'globals' => [
|
): void {
|
||||||
'favicon' => '@App\Service\Favicon',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
if ($containerConfigurator->env() === 'test') {
|
if ($containerConfigurator->env() === 'test') {
|
||||||
$containerConfigurator->extension('twig', [
|
$twig->strictVariables(true);
|
||||||
'strict_variables' => true,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
$twig->formThemes(['bootstrap_5_layout.html.twig']);
|
||||||
|
$twig->fileNamePattern('*.twig');
|
||||||
|
$twig->global('favicon', '@App\Service\Favicon');
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@ 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="assets public bin config migrations src templates composer.json composer.lock symfony.lock .env importmap.php"
|
||||||
|
|
||||||
for path in $pathsToCopy
|
for path in $pathsToCopy
|
||||||
do
|
do
|
||||||
|
|
|
@ -5,4 +5,7 @@ systemctl --user start pod-futtern
|
||||||
sleep 2
|
sleep 2
|
||||||
podman exec -it futtern-php /var/www/html/bin/console cache:clear
|
podman exec -it futtern-php /var/www/html/bin/console cache:clear
|
||||||
podman exec -it futtern-php /var/www/html/bin/console cache:warmup
|
podman exec -it futtern-php /var/www/html/bin/console cache:warmup
|
||||||
|
podman exec -it futtern-php /var/www/html/bin/console asset-map:compile
|
||||||
|
|
||||||
|
|
||||||
echo 'yes' | podman exec -it futtern-php /var/www/html/bin/console doctrine:migrations:migrate
|
echo 'yes' | podman exec -it futtern-php /var/www/html/bin/console doctrine:migrations:migrate
|
||||||
|
|
|
@ -16,4 +16,17 @@ return [
|
||||||
'path' => './assets/app.js',
|
'path' => './assets/app.js',
|
||||||
'entrypoint' => true,
|
'entrypoint' => true,
|
||||||
],
|
],
|
||||||
|
'bootstrap' => [
|
||||||
|
'version' => '5.3.7',
|
||||||
|
],
|
||||||
|
'@popperjs/core' => [
|
||||||
|
'version' => '2.11.8',
|
||||||
|
],
|
||||||
|
'bootstrap/dist/css/bootstrap.min.css' => [
|
||||||
|
'version' => '5.3.7',
|
||||||
|
'type' => 'css',
|
||||||
|
],
|
||||||
|
'htmx.org' => [
|
||||||
|
'version' => '2.0.5',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
47
migrations/Version20250621131822.php
Normal file
47
migrations/Version20250621131822.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250621131822 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE food_vendor ADD COLUMN emojis VARCHAR(30) DEFAULT NULL
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TEMPORARY TABLE __temp__food_vendor AS SELECT name, phone, menu_link, id FROM food_vendor
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE food_vendor
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE food_vendor (name VARCHAR(50) NOT NULL, phone VARCHAR(50) DEFAULT '', menu_link VARCHAR(255) DEFAULT NULL, id BLOB NOT NULL, PRIMARY KEY(id))
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
INSERT INTO food_vendor (name, phone, menu_link, id) SELECT name, phone, menu_link, id FROM __temp__food_vendor
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE __temp__food_vendor
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
<?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
1
public/static/js/htmx.min.js
vendored
1
public/static/js/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -3,48 +3,76 @@
|
||||||
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\OpenFoodOrderProvider;
|
use App\State\LatestOrderProvider;
|
||||||
|
use App\State\OpenOrdersProvider;
|
||||||
use DateInterval;
|
use DateInterval;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Bridge\Doctrine\Types\UlidType;
|
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
use function iterator_to_array;
|
use function iterator_to_array;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: FoodOrderRepository::class)]
|
#[ApiResource(
|
||||||
#[GetCollection(
|
operations: [
|
||||||
uriTemplate: '/food_orders/open',
|
new GetCollection(
|
||||||
provider: OpenFoodOrderProvider::class,
|
uriTemplate: 'food_orders/open',
|
||||||
|
description: 'Get only open orders',
|
||||||
|
provider: OpenOrdersProvider::class,
|
||||||
|
),
|
||||||
|
new Get(
|
||||||
|
uriTemplate: 'food_orders/latest',
|
||||||
|
description: 'Get the latest created order',
|
||||||
|
provider: LatestOrderProvider::class,
|
||||||
|
normalizationContext: [
|
||||||
|
'groups' => ['food_order:read', 'food_order:latest'],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new GetCollection,
|
||||||
|
new Get,
|
||||||
|
new Post,
|
||||||
|
new Put,
|
||||||
|
new Delete,
|
||||||
|
]
|
||||||
)]
|
)]
|
||||||
#[ApiResource]
|
#[ORM\Entity(repositoryClass: FoodOrderRepository::class)]
|
||||||
class FoodOrder
|
class FoodOrder
|
||||||
{
|
{
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
|
#[Groups(['food_order:read'])]
|
||||||
private DateTimeImmutable|null $closedAt = null;
|
private DateTimeImmutable|null $closedAt = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'foodOrders')]
|
#[ORM\ManyToOne(inversedBy: 'foodOrders')]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
#[Groups(['food_order:read', 'food_order:latest'])]
|
||||||
private FoodVendor|null $foodVendor = null;
|
private FoodVendor|null $foodVendor = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, OrderItem>
|
* @var Collection<int, OrderItem>
|
||||||
*/
|
*/
|
||||||
#[ORM\OneToMany(targetEntity: OrderItem::class, mappedBy: 'foodOrder', orphanRemoval: true)]
|
#[ORM\OneToMany(targetEntity: OrderItem::class, mappedBy: 'foodOrder', orphanRemoval: true)]
|
||||||
|
#[Groups(['food_order:read', 'food_order:latest'])]
|
||||||
private Collection $orderItems;
|
private Collection $orderItems;
|
||||||
|
|
||||||
#[ORM\Column(length: 255, options: [
|
#[ORM\Column(length: 255, options: [
|
||||||
'default' => 'nobody',
|
'default' => 'nobody',
|
||||||
])]
|
])]
|
||||||
|
#[Groups(['food_order:read'])]
|
||||||
private string|null $createdBy = 'nobody';
|
private string|null $createdBy = 'nobody';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
||||||
|
#[Groups(['food_order:read'])]
|
||||||
private Ulid|null $id = new Ulid
|
private Ulid|null $id = new Ulid
|
||||||
) {
|
) {
|
||||||
$this->id ??= new Ulid;
|
$this->id ??= new Ulid;
|
||||||
|
@ -57,6 +85,7 @@ class FoodOrder
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Groups(['food_order:read'])]
|
||||||
public function getCreatedAt(): DateTimeImmutable
|
public function getCreatedAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->id->getDateTime();
|
return $this->id->getDateTime();
|
||||||
|
|
|
@ -7,20 +7,27 @@ use App\Repository\FoodVendorRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use InvalidArgumentException;
|
||||||
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
||||||
use Symfony\Bridge\Doctrine\Types\UlidType;
|
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
use Symfony\Component\Validator\Constraints\Length;
|
||||||
|
|
||||||
|
use function mb_strlen;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: FoodVendorRepository::class)]
|
#[ORM\Entity(repositoryClass: FoodVendorRepository::class)]
|
||||||
#[ApiResource]
|
#[ApiResource]
|
||||||
class FoodVendor
|
class FoodVendor
|
||||||
{
|
{
|
||||||
#[ORM\Column(length: 50)]
|
#[ORM\Column(length: 50)]
|
||||||
|
#[Groups(['food_order:latest', 'food_vendor:read'])]
|
||||||
private string|null $name = null;
|
private string|null $name = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 50, nullable: true, options: [
|
#[ORM\Column(length: 50, nullable: true, options: [
|
||||||
'default' => '',
|
'default' => '',
|
||||||
])]
|
])]
|
||||||
|
#[Groups(['food_order:latest', 'food_vendor:read'])]
|
||||||
private string|null $phone = null;
|
private string|null $phone = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,13 +43,23 @@ class FoodVendor
|
||||||
private Collection $menuItems;
|
private Collection $menuItems;
|
||||||
|
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
#[Groups(['food_order:latest'])]
|
||||||
private string|null $menuLink = null;
|
private string|null $menuLink = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String of emojis (max 30 characters)
|
||||||
|
*/
|
||||||
|
#[ORM\Column(length: 30, nullable: true)]
|
||||||
|
#[Groups(['food_order:latest', 'food_vendor:read'])]
|
||||||
|
#[Length(max: 10)]
|
||||||
|
private string|null $emojis = null;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||||
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
||||||
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
|
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
|
||||||
|
#[Groups(['food_order:latest'])]
|
||||||
private Ulid|null $id = new Ulid
|
private Ulid|null $id = new Ulid
|
||||||
) {
|
) {
|
||||||
$this->id ??= new Ulid;
|
$this->id ??= new Ulid;
|
||||||
|
@ -150,4 +167,19 @@ class FoodVendor
|
||||||
$this->phone = $phone;
|
$this->phone = $phone;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEmojis(): string|null
|
||||||
|
{
|
||||||
|
return $this->emojis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEmojis(string|null $emojis): static
|
||||||
|
{
|
||||||
|
if ($emojis !== null && mb_strlen($emojis) > 30) {
|
||||||
|
throw new InvalidArgumentException('A maximum of 30 characters is allowed for emojis');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->emojis = $emojis;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: MenuItemRepository::class)]
|
|
||||||
#[ApiResource]
|
#[ApiResource]
|
||||||
|
#[ORM\Entity(repositoryClass: MenuItemRepository::class)]
|
||||||
class MenuItem
|
class MenuItem
|
||||||
{
|
{
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
|
|
|
@ -7,16 +7,19 @@ use App\Repository\OrderItemRepository;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
||||||
use Symfony\Bridge\Doctrine\Types\UlidType;
|
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: OrderItemRepository::class)]
|
|
||||||
#[ApiResource]
|
#[ApiResource]
|
||||||
|
#[ORM\Entity(repositoryClass: OrderItemRepository::class)]
|
||||||
class OrderItem
|
class OrderItem
|
||||||
{
|
{
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
|
#[Groups(['food_order:latest'])]
|
||||||
private string|null $name = null;
|
private string|null $name = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
#[Groups(['food_order:latest'])]
|
||||||
private string|null $extras = null;
|
private string|null $extras = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'orderItems')]
|
#[ORM\ManyToOne(inversedBy: 'orderItems')]
|
||||||
|
@ -25,11 +28,13 @@ class OrderItem
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
#[Groups(['food_order:latest'])]
|
||||||
private MenuItem|null $menuItem = null;
|
private MenuItem|null $menuItem = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 255, options: [
|
#[ORM\Column(length: 255, options: [
|
||||||
'default' => 'nobody',
|
'default' => 'nobody',
|
||||||
])]
|
])]
|
||||||
|
#[Groups(['food_order:latest'])]
|
||||||
private string|null $createdBy = 'nobody';
|
private string|null $createdBy = 'nobody';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
@ -37,6 +42,7 @@ class OrderItem
|
||||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||||
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
||||||
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
|
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
|
||||||
|
#[Groups(['food_order:latest'])]
|
||||||
private Ulid|null $id = new Ulid
|
private Ulid|null $id = new Ulid
|
||||||
) {
|
) {
|
||||||
$this->id ??= new Ulid;
|
$this->id ??= new Ulid;
|
||||||
|
|
|
@ -17,6 +17,7 @@ final class FoodVendorType extends AbstractType
|
||||||
->add('name')
|
->add('name')
|
||||||
->add('menuLink')
|
->add('menuLink')
|
||||||
->add('phone')
|
->add('phone')
|
||||||
|
->add('emojis')
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,4 +62,13 @@ final class FoodOrderRepository extends ServiceEntityRepository
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getResult();
|
->getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findLatestOrder(): FoodOrder|null
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('alias')
|
||||||
|
->orderBy('alias.id', 'DESC')
|
||||||
|
->setMaxResults(1)
|
||||||
|
->getQuery()
|
||||||
|
->getOneOrNullResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
22
src/State/LatestOrderProvider.php
Normal file
22
src/State/LatestOrderProvider.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?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|null
|
||||||
|
{
|
||||||
|
return $this->repository->findLatestOrder();
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,17 +4,21 @@ 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 OpenFoodOrderProvider implements ProviderInterface
|
final readonly class OpenOrdersProvider 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 = []): object|array|null
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
|
||||||
{
|
{
|
||||||
return $this->repository->findOpenOrders();
|
return $this->repository->findOpenOrders();
|
||||||
}
|
}
|
141
symfony.lock
141
symfony.lock
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"api-platform/symfony": {
|
"api-platform/core": {
|
||||||
"version": "4.0",
|
"version": "4.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "4.0",
|
"version": "4.0",
|
||||||
"ref": "e9952e9f393c2d048f10a78f272cd35e807d972b"
|
"ref": "cb9e6b8ceb9b62f32d41fc8ad72a25d5bd674c6d"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/api_platform.yaml",
|
"config/packages/api_platform.yaml",
|
||||||
|
@ -13,13 +13,22 @@
|
||||||
"src/ApiResource/.gitignore"
|
"src/ApiResource/.gitignore"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"doctrine/doctrine-bundle": {
|
"doctrine/deprecations": {
|
||||||
"version": "2.12",
|
"version": "1.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "2.12",
|
"version": "1.0",
|
||||||
"ref": "7266981c201efbbe02ae53c87f8bb378e3f825ae"
|
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"doctrine/doctrine-bundle": {
|
||||||
|
"version": "2.14",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "2.13",
|
||||||
|
"ref": "620b57f496f2e599a6015a9fa222c2ee0a32adcb"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/doctrine.yaml",
|
"config/packages/doctrine.yaml",
|
||||||
|
@ -28,7 +37,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"doctrine/doctrine-fixtures-bundle": {
|
"doctrine/doctrine-fixtures-bundle": {
|
||||||
"version": "4.0",
|
"version": "4.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -40,7 +49,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"doctrine/doctrine-migrations-bundle": {
|
"doctrine/doctrine-migrations-bundle": {
|
||||||
"version": "3.3",
|
"version": "3.4",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -53,7 +62,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"liip/test-fixtures-bundle": {
|
"liip/test-fixtures-bundle": {
|
||||||
"version": "3.2.1"
|
"version": "3.4.0"
|
||||||
},
|
},
|
||||||
"nelmio/cors-bundle": {
|
"nelmio/cors-bundle": {
|
||||||
"version": "2.5",
|
"version": "2.5",
|
||||||
|
@ -68,39 +77,46 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"phpstan/phpstan": {
|
"phpstan/phpstan": {
|
||||||
"version": "1.11",
|
"version": "1.12",
|
||||||
"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.4",
|
"version": "11.5",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "9.6",
|
"version": "11.1",
|
||||||
"ref": "7364a21d87e658eb363c5020c072ecfdc12e2326"
|
"ref": "c6658a60fc9d594805370eacdf542c3d6b5c0869"
|
||||||
},
|
},
|
||||||
"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.10",
|
"version": "3.13",
|
||||||
"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/asset-mapper": {
|
"symfony/asset-mapper": {
|
||||||
"version": "7.2",
|
"version": "7.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -115,7 +131,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/console": {
|
"symfony/console": {
|
||||||
"version": "7.1",
|
"version": "7.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -127,24 +143,37 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/flex": {
|
"symfony/flex": {
|
||||||
"version": "2.4",
|
"version": "2.7",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "1.0",
|
"version": "2.4",
|
||||||
"ref": "146251ae39e06a95be0fe3d13c807bcf3938b172"
|
"ref": "52e9754527a15e2b79d9a610f98185a1fe46622a"
|
||||||
},
|
},
|
||||||
"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.1",
|
"version": "7.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "7.0",
|
"version": "7.3",
|
||||||
"ref": "6356c19b9ae08e7763e4ba2d9ae63043efc75db5"
|
"ref": "5a1497d539f691b96afd45ae397ce5fe30beb4b9"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/cache.yaml",
|
"config/packages/cache.yaml",
|
||||||
|
@ -154,11 +183,12 @@
|
||||||
"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.60",
|
"version": "1.63",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -166,8 +196,32 @@
|
||||||
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"symfony/monolog-bundle": {
|
||||||
|
"version": "3.10",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "3.7",
|
||||||
|
"ref": "aff23899c4440dd995907613c1dd709b6f59503f"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/monolog.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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.1",
|
"version": "7.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -180,7 +234,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/security-bundle": {
|
"symfony/security-bundle": {
|
||||||
"version": "7.2",
|
"version": "7.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -193,7 +247,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/twig-bundle": {
|
"symfony/twig-bundle": {
|
||||||
"version": "7.1",
|
"version": "7.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -206,7 +260,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/uid": {
|
"symfony/uid": {
|
||||||
"version": "7.1",
|
"version": "7.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -215,7 +269,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"symfony/validator": {
|
"symfony/validator": {
|
||||||
"version": "7.1",
|
"version": "7.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -227,28 +281,19 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/web-profiler-bundle": {
|
"symfony/web-profiler-bundle": {
|
||||||
"version": "7.1",
|
"version": "7.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "6.1",
|
"version": "7.3",
|
||||||
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
|
"ref": "5b2b543e13942495c0003f67780cb4448af9e606"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/web_profiler.yaml",
|
"config/packages/web_profiler.yaml",
|
||||||
"config/routes/web_profiler.yaml"
|
"config/routes/web_profiler.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfonycasts/tailwind-bundle": {
|
"twig/extra-bundle": {
|
||||||
"version": "0.10",
|
"version": "v3.21.0"
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "0.8",
|
|
||||||
"ref": "4ea7c9488fdce8943520daf3fdc31e93e5b59c64"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"config/packages/symfonycasts_tailwind.yaml"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
content: [
|
|
||||||
"./assets/**/*.js",
|
|
||||||
"./templates/**/*.html.twig",
|
|
||||||
],
|
|
||||||
darkMode: 'media', // Use 'media' to respect the user's system preference
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
}
|
|
|
@ -1,24 +1,4 @@
|
||||||
{{ form_start(form, {'attr': {'class': 'space-y-6'}}) }}
|
{{ form_start(form, {'attr': {'class': 'mb-3'}}) }}
|
||||||
{% for field in form %}
|
{{ form_widget(form, {'attr': {'class': 'form-control'}}) }}
|
||||||
{% if field.vars.name != '_token' %}
|
<button class="btn btn-primary mt-2">{{ button_label|default('Save') }}</button>
|
||||||
<div class="space-y-2">
|
|
||||||
{{ form_label(field, null, {'label_attr': {'class': 'block text-sm font-medium leading-6 text-gray-900 dark:text-gray-100'}}) }}
|
|
||||||
<div>
|
|
||||||
{{ form_widget(field, {'attr': {'class': 'block w-full rounded-md border-0 py-1.5 text-gray-900 dark:text-gray-100 bg-white dark:bg-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:ring-2 focus:ring-inset focus:ring-indigo-600 dark:focus:ring-indigo-500 sm:text-sm sm:leading-6'}}) }}
|
|
||||||
</div>
|
|
||||||
{% if field.vars.errors|length > 0 %}
|
|
||||||
<div class="mt-1 text-sm text-red-600 dark:text-red-400">
|
|
||||||
{{ form_errors(field) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if field.vars.help is defined and field.vars.help %}
|
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ field.vars.help }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
<div class="mt-6 flex items-center justify-end gap-x-6">
|
|
||||||
<a href="{{ app.request.headers.get('referer', '/') }}" class="text-sm font-semibold leading-6 text-gray-900 dark:text-gray-100">Cancel</a>
|
|
||||||
<button type="submit" class="rounded-md bg-indigo-600 dark:bg-indigo-700 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:hover:bg-indigo-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:focus-visible:outline-indigo-500">{{ button_label|default('Save') }}</button>
|
|
||||||
</div>
|
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
|
@ -1,93 +1,65 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="h-full bg-gray-100 dark:bg-gray-900">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<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)">
|
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||||
<meta name="theme-color" content="#222222" media="(prefers-color-scheme: dark)">
|
<link rel="icon" type="image/svg+xml"
|
||||||
<title>{% block title %}Welcome!{% endblock %} - Futtern</title>
|
href="{{ favicon }}" />
|
||||||
<link rel="icon" type="image/svg+xml" href="{{ favicon }}" />
|
|
||||||
<script src="/static/js/htmx.min.js"></script>
|
|
||||||
{% block stylesheets %}
|
|
||||||
<link rel="stylesheet" href="{{ asset('styles/app.css') }}">
|
|
||||||
{% endblock %}
|
|
||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
{{ importmap() }}
|
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body class="h-full dark:text-gray-100">
|
<body>
|
||||||
<div class="min-h-full">
|
<header class="mb-4">
|
||||||
<nav class="bg-indigo-600 dark:bg-indigo-800">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
<div class="container-fluid">
|
||||||
<div class="flex h-16 items-center justify-between">
|
<span class="navbar-brand">Futtern</span>
|
||||||
<div class="flex items-center">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<div class="flex-shrink-0">
|
<span class="navbar-toggler-icon"></span>
|
||||||
<span class="text-white text-xl font-bold">Futtern</span>
|
</button>
|
||||||
</div>
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
<div class="hidden md:block">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
<div class="ml-10 flex items-baseline space-x-4">
|
<li class="nav-item"><a class="nav-link" href="{{ path('app_food_order_index') }}">Orders</a></li>
|
||||||
<a href="{{ path('app_food_order_index') }}" class="text-white hover:bg-indigo-500 hover:bg-opacity-75 dark:hover:bg-indigo-700 rounded-md px-3 py-2 text-sm font-medium">Orders</a>
|
<li class="nav-item"><a class="nav-link" href="{{ path('app_food_vendor_index') }}">Vendors</a></li>
|
||||||
<a href="{{ path('app_food_vendor_index') }}" class="text-white hover:bg-indigo-500 hover:bg-opacity-75 dark:hover:bg-indigo-700 rounded-md px-3 py-2 text-sm font-medium">Vendors</a>
|
<li class="nav-item"><a class="nav-link" href="/api">API</a></li>
|
||||||
<a href="https://git.hannover.ccc.de/lubiana/futtern/issues/new" target="_blank" class="text-white hover:bg-indigo-500 hover:bg-opacity-75 dark:hover:bg-indigo-700 rounded-md px-3 py-2 text-sm font-medium">Create Issue</a>
|
<li class="nav-item"><a class="nav-link" href="https://git.hannover.ccc.de/lubiana/futtern/issues/new" target="_blank">Create Issue</a></li>
|
||||||
<a href="/api" class="text-white hover:bg-indigo-500 hover:bg-opacity-75 dark:hover:bg-indigo-700 rounded-md px-3 py-2 text-sm font-medium">API</a>
|
</ul>
|
||||||
</div>
|
<div class="btn-group ms-auto" role="group" aria-label="Mode selection">
|
||||||
</div>
|
<input type="radio" class="btn-check" name="mode" id="normal" autocomplete="off" checked>
|
||||||
</div>
|
<label class="btn btn-outline-primary" for="normal">
|
||||||
<div class="hidden md:block">
|
Normal
|
||||||
<div class="ml-4 flex items-center md:ml-6">
|
<span class="emoji-normal">😌</span>
|
||||||
<div class="text-white">
|
<span class="emoji-enhanced">😌🍑</span>
|
||||||
Hello {{ app.request.cookies.get('username', 'nobody') }} -
|
<span class="emoji-bonkers">😌🍑🍆💦👅💋😈🏳️🌈✨</span>
|
||||||
<a href="{{ path('username') }}" class="text-indigo-200 hover:text-white">change name</a>
|
</label>
|
||||||
</div>
|
|
||||||
</div>
|
<input type="radio" class="btn-check" name="mode" id="enhanced" autocomplete="off">
|
||||||
</div>
|
<label class="btn btn-outline-primary" for="enhanced">
|
||||||
<div class="-mr-2 flex md:hidden">
|
Enhanced
|
||||||
<!-- Mobile menu button -->
|
<span class="emoji-normal">✨</span>
|
||||||
<button type="button" class="relative inline-flex items-center justify-center rounded-md bg-indigo-600 dark:bg-indigo-800 p-2 text-indigo-200 hover:bg-indigo-500 dark:hover:bg-indigo-700 hover:bg-opacity-75 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-indigo-600 dark:focus:ring-offset-indigo-800" aria-controls="mobile-menu" aria-expanded="false">
|
<span class="emoji-enhanced">✨🍑🍆</span>
|
||||||
<span class="absolute -inset-0.5"></span>
|
<span class="emoji-bonkers">✨🍑🍆💦👅💋😈🏳️🌈</span>
|
||||||
<span class="sr-only">Open main menu</span>
|
</label>
|
||||||
<!-- Menu open: "hidden", Menu closed: "block" -->
|
|
||||||
<svg class="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
<input type="radio" class="btn-check" name="mode" id="bonkers" autocomplete="off">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
<label class="btn btn-outline-primary" for="bonkers">
|
||||||
</svg>
|
Bonkers
|
||||||
</button>
|
<span class="emoji-normal">💦</span>
|
||||||
</div>
|
<span class="emoji-enhanced">💦🍑🍆</span>
|
||||||
</div>
|
<span class="emoji-bonkers">💦🍑🍆👅💋😈🏳️🌈✨🥵😳🤤😍🥴💕💗💘💝💞💟💌💏💑</span>
|
||||||
</div>
|
</label>
|
||||||
|
|
||||||
<!-- Mobile menu, show/hide based on menu state. -->
|
|
||||||
<div class="md:hidden" id="mobile-menu">
|
|
||||||
<div class="space-y-1 px-2 pb-3 pt-2 sm:px-3">
|
|
||||||
<a href="{{ path('app_food_order_index') }}" class="text-white hover:bg-indigo-500 dark:hover:bg-indigo-700 hover:bg-opacity-75 block rounded-md px-3 py-2 text-base font-medium">Orders</a>
|
|
||||||
<a href="{{ path('app_food_vendor_index') }}" class="text-white hover:bg-indigo-500 dark:hover:bg-indigo-700 hover:bg-opacity-75 block rounded-md px-3 py-2 text-base font-medium">Vendors</a>
|
|
||||||
<a href="https://git.hannover.ccc.de/lubiana/futtern/issues/new" target="_blank" class="text-white hover:bg-indigo-500 dark:hover:bg-indigo-700 hover:bg-opacity-75 block rounded-md px-3 py-2 text-base font-medium">Create Issue</a>
|
|
||||||
<a href="/api" class="text-white hover:bg-indigo-500 dark:hover:bg-indigo-700 hover:bg-opacity-75 block rounded-md px-3 py-2 text-base font-medium">API</a>
|
|
||||||
</div>
|
|
||||||
<div class="border-t border-indigo-700 pb-3 pt-4">
|
|
||||||
<div class="flex items-center px-5">
|
|
||||||
<div class="text-base font-medium text-white">
|
|
||||||
Hello {{ app.request.cookies.get('username', 'nobody') }} -
|
|
||||||
<a href="{{ path('username') }}" class="text-indigo-200 hover:text-white">change name</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<span class="navbar-text">
|
||||||
|
Hello {{ app.request.cookies.get('username', 'nobody') }} - <a class="text-light" href="{{ path('username') }}">change name</a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
</header>
|
||||||
<header class="bg-white dark:bg-gray-800 shadow">
|
<main class="container pb-5">
|
||||||
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
|
{% block body %}{% endblock %}
|
||||||
<h1 class="text-3xl font-bold tracking-tight text-gray-900 dark:text-white">{% block header %}{% endblock %}</h1>
|
</main>
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<div class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
|
|
||||||
<div class="px-4 py-6 sm:px-0">
|
|
||||||
{% block body %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
{% block title %}Edit FoodOrder{% endblock %}
|
{% block title %}Edit FoodOrder{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>Edit FoodOrder</h1>
|
<h1 class="mb-4">Edit FoodOrder</h1>
|
||||||
|
|
||||||
{{ include('_form.html.twig', {'button_label': 'Update'}) }}
|
<div class="mb-4">
|
||||||
|
{{ include('_form.html.twig', {'button_label': 'Update'}) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="{{ path('app_food_order_index') }}">back to list</a>
|
<a class="btn btn-secondary" href="{{ path('app_food_order_index') }}">back to list</a>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,69 +1,48 @@
|
||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Food Orders{% endblock %}
|
{% block title %}FoodOrder index{% endblock %}
|
||||||
|
|
||||||
{% block header %}Food Orders{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="sm:flex sm:items-center sm:justify-between mb-6">
|
<h1 class="mb-4">FoodOrder index</h1>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="mb-3">
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<button
|
||||||
Manage your food orders and create new ones.
|
class="btn btn-primary"
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4 flex sm:ml-4 sm:mt-0">
|
|
||||||
<button
|
|
||||||
hx-get="{{ path('app_food_order_new') }}"
|
hx-get="{{ path('app_food_order_new') }}"
|
||||||
hx-trigger="click"
|
hx-trigger="click"
|
||||||
hx-target="#new-order-form"
|
hx-target="closest div"
|
||||||
class="block rounded-md bg-indigo-600 dark:bg-indigo-700 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:hover:bg-indigo-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:focus-visible:outline-indigo-500"
|
>Create new</button>
|
||||||
>Create new order</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
<div id="new-order-form" class="mb-8"></div>
|
<table class="table table-striped table-hover">
|
||||||
|
<thead class="table-light">
|
||||||
<div class="mt-8 flow-root">
|
<tr>
|
||||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
<th>CreatedBy</th>
|
||||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
<th>Vendor</th>
|
||||||
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 dark:ring-white dark:ring-opacity-10 sm:rounded-lg">
|
<th>CreatedAt</th>
|
||||||
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-700">
|
<th>ClosedAt</th>
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
<th>actions</th>
|
||||||
<tr>
|
</tr>
|
||||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100 sm:pl-6">Created By</th>
|
</thead>
|
||||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Vendor</th>
|
<tbody>
|
||||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Created At</th>
|
{% for food_order in food_orders %}
|
||||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Closed At</th>
|
{{ include('food_order/table_row.html.twig') }}
|
||||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
{% endfor %}
|
||||||
<span class="sr-only">Actions</span>
|
{% if food_orders|length < 10 %}
|
||||||
</th>
|
<tr>
|
||||||
</tr>
|
<td colspan="5" class="text-center text-muted">
|
||||||
</thead>
|
check the <a href="{{ path('app_food_order_archive') }}">archive</a>
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
for older orders
|
||||||
{% for food_order in food_orders %}
|
</td>
|
||||||
{{ include('food_order/table_row.html.twig') }}
|
</tr>
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
{% if food_orders|length < 10 %}
|
</tbody>
|
||||||
<tr>
|
</table>
|
||||||
<td colspan="5" class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-500 dark:text-gray-400 sm:pl-6">
|
<div class="d-flex gap-2">
|
||||||
Check the <a href="{{ path('app_food_order_archive') }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">archive</a>
|
|
||||||
for older orders
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6 flex items-center justify-center gap-x-6">
|
|
||||||
{% if prev_page > 0 %}
|
{% if prev_page > 0 %}
|
||||||
<a href="{{ path('app_food_order_archive', {'page': prev_page}) }}" class="rounded-md bg-white dark:bg-gray-700 px-3.5 py-2.5 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">Previous Page</a>
|
<a class="btn btn-outline-secondary btn-sm" href="{{ path('app_food_order_archive', {'page': prev_page}) }}">previous page</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if next_page > current_page %}
|
{% if next_page > current_page %}
|
||||||
<a href="{{ path('app_food_order_archive', {'page': next_page}) }}" class="rounded-md bg-white dark:bg-gray-700 px-3.5 py-2.5 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">Next Page</a>
|
<a class="btn btn-outline-secondary btn-sm" href="{{ path('app_food_order_archive', {'page': next_page}) }}">next page</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
<div class="mb-4">
|
||||||
<div class="px-4 py-5 sm:p-6">
|
{{ include('_form.html.twig') }}
|
||||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">Create New Order</h3>
|
|
||||||
<div class="mt-2 max-w-xl text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
<p>Fill out the form below to create a new food order.</p>
|
|
||||||
</div>
|
|
||||||
<div class="mt-5">
|
|
||||||
{{ include('_form.html.twig') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,101 +1,73 @@
|
||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Order Details{% endblock %}
|
{% block title %}FoodOrder{% endblock %}
|
||||||
|
|
||||||
{% block header %}Order Details{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg mb-8">
|
<h1 class="mb-4">FoodOrder</h1>
|
||||||
<div class="px-4 py-5 sm:px-6">
|
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-white">Order Information</h3>
|
|
||||||
<p class="mt-1 max-w-2xl text-sm text-gray-500 dark:text-gray-400">Details about the food order from {{ food_order.foodVendor.name }}.</p>
|
|
||||||
</div>
|
|
||||||
<div class="border-t border-gray-200 dark:border-gray-700">
|
|
||||||
<dl>
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-300">Vendor</dt>
|
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100 sm:col-span-2 sm:mt-0">{{ food_order.foodVendor.name }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white dark:bg-gray-800 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-300">Vendor Phone</dt>
|
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100 sm:col-span-2 sm:mt-0">{{ food_order.foodVendor.phone }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-300">Created By</dt>
|
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100 sm:col-span-2 sm:mt-0">{{ food_order.createdBy }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white dark:bg-gray-800 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-300">Created At</dt>
|
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100 sm:col-span-2 sm:mt-0">{{ food_order.createdAt ? food_order.createdAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-300">Closed At</dt>
|
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100 sm:col-span-2 sm:mt-0">{{ food_order.closedAt ? food_order.closedAt|date('Y-m-d H:i:s', 'Europe/Berlin') : 'Not closed yet' }}</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex space-x-4 mb-8">
|
<table class="table table-bordered table-striped w-auto">
|
||||||
<a href="{{ path('app_food_order_index') }}" class="inline-flex items-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">
|
<tbody>
|
||||||
Back to list
|
<tr>
|
||||||
</a>
|
<th>Vendor</th>
|
||||||
|
<td>{{ food_order.foodVendor.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Vendorphone</th>
|
||||||
|
<td>{{ food_order.foodVendor.phone }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Created By</th>
|
||||||
|
<td>{{ food_order.createdBy }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>CreatedAt</th>
|
||||||
|
<td>{{ food_order.createdAt ? food_order.createdAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>ClosedAt</th>
|
||||||
|
<td>{{ food_order.closedAt ? food_order.closedAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="mb-3 d-flex gap-2">
|
||||||
|
<a class="btn btn-secondary" href="{{ path('app_food_order_index') }}">back to list</a>
|
||||||
{% if(food_order.isClosed) %}
|
{% if(food_order.isClosed) %}
|
||||||
<a href="{{ path('app_food_order_open', {'id': food_order.id}) }}" class="inline-flex items-center rounded-md bg-green-600 dark:bg-green-700 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 dark:hover:bg-green-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600 dark:focus-visible:outline-green-500">
|
<a class="btn btn-warning" href="{{ path('app_food_order_open', {'id': food_order.id}) }}">reopen</a>
|
||||||
Reopen Order
|
|
||||||
</a>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{ path('app_food_order_close', {'id': food_order.id}) }}" class="inline-flex items-center rounded-md bg-red-600 dark:bg-red-700 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 dark:hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 dark:focus-visible:outline-red-500">
|
<a class="btn btn-success" href="{{ path('app_food_order_close', {'id': food_order.id}) }}">close</a>
|
||||||
Close Order
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg mb-8">
|
<h2 class="mt-5">Items</h2>
|
||||||
<div class="px-4 py-5 sm:px-6 flex justify-between items-center">
|
<table class="table table-hover">
|
||||||
<div>
|
<thead class="table-light">
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-white">Order Items</h3>
|
<tr>
|
||||||
<p class="mt-1 max-w-2xl text-sm text-gray-500 dark:text-gray-400">Items included in this order.</p>
|
<th>Index</th>
|
||||||
</div>
|
<th>username</th>
|
||||||
<div>
|
<th>name</th>
|
||||||
<a href="{{ path('app_order_item_new', {'foodOrder': food_order.id}) }}" class="inline-flex items-center rounded-md bg-indigo-600 dark:bg-indigo-700 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:hover:bg-indigo-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:focus-visible:outline-indigo-500">
|
<th>extras</th>
|
||||||
Add New Item
|
<th>actions</th>
|
||||||
</a>
|
</tr>
|
||||||
</div>
|
</thead>
|
||||||
</div>
|
<tbody>
|
||||||
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 dark:ring-white dark:ring-opacity-10">
|
{% for item in food_order.orderItemsSortedByName %}
|
||||||
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-700">
|
<tr>
|
||||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
<td>{{ loop.index }}</td>
|
||||||
<tr>
|
<td>{{ item.createdBy }}</td>
|
||||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100 sm:pl-6">#</th>
|
<td>{{ item.name }}</td>
|
||||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Username</th>
|
<td>{{ item.extras }}</td>
|
||||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Item Name</th>
|
<td>
|
||||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Extras</th>
|
{% if(food_order.isClosed) %}
|
||||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
{% else %}
|
||||||
<span class="sr-only">Actions</span>
|
<a class="btn btn-sm btn-outline-primary me-1" href="{{ path('app_order_item_edit', {'id': item.id}) }}">edit</a>
|
||||||
</th>
|
<a class="btn btn-sm btn-outline-secondary me-1" href="{{ path('app_order_item_copy', {'id': item.id}) }}">copy</a>
|
||||||
</tr>
|
<a class="btn btn-sm btn-outline-danger" href="{{ path('app_order_item_delete', {'id': item.id}) }}">remove</a>
|
||||||
</thead>
|
{% endif %}
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
</td>
|
||||||
{% for item in food_order.orderItemsSortedByName %}
|
</tr>
|
||||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
{% endfor %}
|
||||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 dark:text-gray-100 sm:pl-6">{{ loop.index }}</td>
|
</tbody>
|
||||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">{{ item.createdBy }}</td>
|
</table>
|
||||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">{{ item.name }}</td>
|
<a class="btn btn-primary mt-2" href="{{ path('app_order_item_new', {'foodOrder': food_order.id}) }}">New Item</a>
|
||||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">{{ item.extras }}</td>
|
|
||||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
|
||||||
{% if(food_order.isClosed) %}
|
|
||||||
<span class="text-gray-400 dark:text-gray-500">Order closed</span>
|
|
||||||
{% else %}
|
|
||||||
<a href="{{ path('app_order_item_edit', {'id': item.id}) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 mr-2">Edit</a>
|
|
||||||
<a href="{{ path('app_order_item_copy', {'id': item.id}) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 mr-2">Copy</a>
|
|
||||||
<a href="{{ path('app_order_item_delete', {'id': item.id}) }}" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">Remove</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
<tr>
|
||||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 dark:text-gray-100 sm:pl-6">{{ food_order.createdBy }}</td>
|
<td>{{ food_order.createdBy }}</td>
|
||||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">{{ food_order.foodVendor.name }}</td>
|
<td>{{ food_order.foodVendor.name }}</td>
|
||||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">{{ food_order.createdAt ? food_order.createdAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td>
|
<td>{{ food_order.createdAt ? food_order.createdAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td>
|
||||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">{{ food_order.closedAt ? food_order.closedAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td>
|
<td>{{ food_order.closedAt ? food_order.closedAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td>
|
||||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
<td>
|
||||||
<a href="{{ path('app_food_order_show', {'id': food_order.id}) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">View<span class="sr-only">, order from {{ food_order.createdBy }}</span></a>
|
<a class="btn btn-sm btn-outline-info" href="{{ path('app_food_order_show', {'id': food_order.id}) }}">show</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
|
@ -1,24 +1,4 @@
|
||||||
{{ form_start(form, {'attr': {'class': 'space-y-6'}}) }}
|
{{ form_start(form, {'attr': {'class': 'mb-3'}}) }}
|
||||||
{% for field in form %}
|
{{ form_widget(form, {'attr': {'class': 'form-control'}}) }}
|
||||||
{% if field.vars.name != '_token' %}
|
<button class="btn btn-primary mt-2">{{ button_label|default('Save') }}</button>
|
||||||
<div class="space-y-2">
|
|
||||||
{{ form_label(field, null, {'label_attr': {'class': 'block text-sm font-medium leading-6 text-gray-900'}}) }}
|
|
||||||
<div>
|
|
||||||
{{ form_widget(field, {'attr': {'class': 'block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6'}}) }}
|
|
||||||
</div>
|
|
||||||
{% if field.vars.errors|length > 0 %}
|
|
||||||
<div class="mt-1 text-sm text-red-600">
|
|
||||||
{{ form_errors(field) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if field.vars.help is defined and field.vars.help %}
|
|
||||||
<p class="mt-1 text-sm text-gray-500">{{ field.vars.help }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
<div class="mt-6 flex items-center justify-end gap-x-6">
|
|
||||||
<a href="{{ app.request.headers.get('referer', '/') }}" class="text-sm font-semibold leading-6 text-gray-900">Cancel</a>
|
|
||||||
<button type="submit" class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">{{ button_label|default('Save') }}</button>
|
|
||||||
</div>
|
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
|
@ -3,9 +3,127 @@
|
||||||
{% block title %}Edit FoodVendor{% endblock %}
|
{% block title %}Edit FoodVendor{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>Edit FoodVendor</h1>
|
<h1 class="mb-4">Edit FoodVendor</h1>
|
||||||
|
|
||||||
{{ include('food_vendor/_form.html.twig', {'button_label': 'Update'}) }}
|
<div class="mb-4">
|
||||||
|
{{ include('food_vendor/_form.html.twig', {'button_label': 'Update'}) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="{{ path('app_food_vendor_index') }}">back to list</a>
|
<div class="mb-4 emoji-buttons" data-role="emoji-selector">
|
||||||
|
<button class="btn btn-primary">🍕</button>
|
||||||
|
<button class="btn btn-primary">🍔</button>
|
||||||
|
<button class="btn btn-primary">🌮</button>
|
||||||
|
<button class="btn btn-primary">🍜</button>
|
||||||
|
<button class="btn btn-primary">🥗</button>
|
||||||
|
<button class="btn btn-primary">☕</button>
|
||||||
|
<button class="btn btn-primary">🍣</button>
|
||||||
|
<button class="btn btn-primary">🍤</button>
|
||||||
|
<button class="btn btn-primary">🍦</button>
|
||||||
|
<button class="btn btn-primary">🍩</button>
|
||||||
|
<button class="btn btn-primary">🍪</button>
|
||||||
|
<button class="btn btn-primary">🍰</button>
|
||||||
|
<button class="btn btn-primary">🍫</button>
|
||||||
|
<button class="btn btn-primary">🍿</button>
|
||||||
|
<button class="btn btn-primary">🍩</button>
|
||||||
|
<button class="btn btn-primary">🍭</button>
|
||||||
|
<button class="btn btn-primary">🍮</button>
|
||||||
|
<button class="btn btn-primary">🍯</button>
|
||||||
|
<button class="btn btn-primary">🍎</button>
|
||||||
|
<button class="btn btn-primary">🍊</button>
|
||||||
|
<button class="btn btn-primary">🍋</button>
|
||||||
|
<button class="btn btn-primary">🍌</button>
|
||||||
|
<button class="btn btn-primary">🍉</button>
|
||||||
|
<button class="btn btn-primary">🍇</button>
|
||||||
|
<button class="btn btn-primary">🍓</button>
|
||||||
|
<button class="btn btn-primary">🍈</button>
|
||||||
|
<button class="btn btn-primary">🍒</button>
|
||||||
|
<button class="btn btn-primary">🍍</button>
|
||||||
|
<button class="btn btn-primary">🥭</button>
|
||||||
|
<button class="btn btn-primary">🥥</button>
|
||||||
|
<button class="btn btn-primary">🥝</button>
|
||||||
|
<button class="btn btn-primary">🍅</button>
|
||||||
|
<button class="btn btn-primary">🍆</button>
|
||||||
|
<button class="btn btn-primary">🥑</button>
|
||||||
|
<button class="btn btn-primary">🥦</button>
|
||||||
|
<button class="btn btn-primary">🥒</button>
|
||||||
|
<button class="btn btn-primary">🌽</button>
|
||||||
|
<button class="btn btn-primary">🥕</button>
|
||||||
|
<button class="btn btn-primary">🧄</button>
|
||||||
|
<button class="btn btn-primary">🧅</button>
|
||||||
|
<button class="btn btn-primary">🥔</button>
|
||||||
|
<button class="btn btn-primary">🍠</button>
|
||||||
|
<button class="btn btn-primary">🥐</button>
|
||||||
|
<button class="btn btn-primary">🥯</button>
|
||||||
|
<button class="btn btn-primary">🍞</button>
|
||||||
|
<button class="btn btn-primary">🥖</button>
|
||||||
|
<button class="btn btn-primary">🥨</button>
|
||||||
|
<button class="btn btn-primary">🧀</button>
|
||||||
|
<button class="btn btn-primary">🥚</button>
|
||||||
|
<button class="btn btn-primary">🍳</button>
|
||||||
|
<button class="btn btn-primary">🥞</button>
|
||||||
|
<button class="btn btn-primary">🧇</button>
|
||||||
|
<button class="btn btn-primary">🥓</button>
|
||||||
|
<button class="btn btn-primary">🥩</button>
|
||||||
|
<button class="btn btn-primary">🍗</button>
|
||||||
|
<button class="btn btn-primary">🍖</button>
|
||||||
|
<button class="btn btn-primary">🌭</button>
|
||||||
|
<button class="btn btn-primary">🍔</button>
|
||||||
|
<button class="btn btn-primary">🍟</button>
|
||||||
|
<button class="btn btn-primary">🍕</button>
|
||||||
|
<button class="btn btn-primary">🥪</button>
|
||||||
|
<button class="btn btn-primary">🌮</button>
|
||||||
|
<button class="btn btn-primary">🌯</button>
|
||||||
|
<button class="btn btn-primary">🥙</button>
|
||||||
|
<button class="btn btn-primary">🧆</button>
|
||||||
|
<button class="btn btn-primary">🥘</button>
|
||||||
|
<button class="btn btn-primary">🍲</button>
|
||||||
|
<button class="btn btn-primary">🥣</button>
|
||||||
|
<button class="btn btn-primary">🥗</button>
|
||||||
|
<button class="btn btn-primary">🍿</button>
|
||||||
|
<button class="btn btn-primary">🧈</button>
|
||||||
|
<button class="btn btn-primary">🧂</button>
|
||||||
|
<button class="btn btn-primary">🥫</button>
|
||||||
|
<button class="btn btn-primary">🍱</button>
|
||||||
|
<button class="btn btn-primary">🍛</button>
|
||||||
|
<button class="btn btn-primary">🍚</button>
|
||||||
|
<button class="btn btn-primary">🍙</button>
|
||||||
|
<button class="btn btn-primary">🍘</button>
|
||||||
|
<button class="btn btn-primary">🍢</button>
|
||||||
|
<button class="btn btn-primary">🍡</button>
|
||||||
|
<button class="btn btn-primary">🍧</button>
|
||||||
|
<button class="btn btn-primary">🍨</button>
|
||||||
|
<button class="btn btn-primary">🍦</button>
|
||||||
|
<button class="btn btn-primary">🥧</button>
|
||||||
|
<button class="btn btn-primary">🍰</button>
|
||||||
|
<button class="btn btn-primary">🎂</button>
|
||||||
|
<button class="btn btn-primary">🍮</button>
|
||||||
|
<button class="btn btn-primary">🍭</button>
|
||||||
|
<button class="btn btn-primary">🍬</button>
|
||||||
|
<button class="btn btn-primary">🍫</button>
|
||||||
|
<button class="btn btn-primary">🍿</button>
|
||||||
|
<button class="btn btn-primary">🍩</button>
|
||||||
|
<button class="btn btn-primary">🍪</button>
|
||||||
|
<button class="btn btn-primary">🌰</button>
|
||||||
|
<button class="btn btn-primary">🥜</button>
|
||||||
|
<button class="btn btn-primary">🍯</button>
|
||||||
|
<button class="btn btn-primary">🥛</button>
|
||||||
|
<button class="btn btn-primary">🍼</button>
|
||||||
|
<button class="btn btn-primary">☕</button>
|
||||||
|
<button class="btn btn-primary">🍵</button>
|
||||||
|
<button class="btn btn-primary">🍶</button>
|
||||||
|
<button class="btn btn-primary">🍾</button>
|
||||||
|
<button class="btn btn-primary">🍷</button>
|
||||||
|
<button class="btn btn-primary">🍸</button>
|
||||||
|
<button class="btn btn-primary">🍹</button>
|
||||||
|
<button class="btn btn-primary">🍺</button>
|
||||||
|
<button class="btn btn-primary">🍻</button>
|
||||||
|
<button class="btn btn-primary">🥂</button>
|
||||||
|
<button class="btn btn-primary">🥃</button>
|
||||||
|
<button class="btn btn-primary">🥤</button>
|
||||||
|
<button class="btn btn-primary">🧃</button>
|
||||||
|
<button class="btn btn-primary">🧉</button>
|
||||||
|
<button class="btn btn-primary">🧊</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a class="btn btn-secondary" href="{{ path('app_food_vendor_index') }}">back to list</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,54 +1,33 @@
|
||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Food Vendors{% endblock %}
|
{% block title %}FoodVendor index{% endblock %}
|
||||||
|
|
||||||
{% block header %}Food Vendors{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="sm:flex sm:items-center sm:justify-between mb-6">
|
<h1 class="mb-4">FoodVendor index</h1>
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
Manage your food vendors and create new ones.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4 flex sm:ml-4 sm:mt-0">
|
|
||||||
<a href="{{ path('app_food_vendor_new') }}" class="block rounded-md bg-indigo-600 dark:bg-indigo-700 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:hover:bg-indigo-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:focus-visible:outline-indigo-500">
|
|
||||||
Create new vendor
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-8 flow-root">
|
<table class="table table-striped table-hover">
|
||||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
<thead class="table-light">
|
||||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
<tr>
|
||||||
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 dark:ring-white dark:ring-opacity-10 sm:rounded-lg">
|
<th>Name</th>
|
||||||
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-700">
|
<th>actions</th>
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
</tr>
|
||||||
<tr>
|
</thead>
|
||||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100 sm:pl-6">Name</th>
|
<tbody>
|
||||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
{% for food_vendor in food_vendors %}
|
||||||
<span class="sr-only">Actions</span>
|
<tr>
|
||||||
</th>
|
<td>{{ food_vendor.name }}</td>
|
||||||
</tr>
|
<td>
|
||||||
</thead>
|
<a class="btn btn-sm btn-outline-info me-1" href="{{ path('app_food_vendor_show', {'id': food_vendor.id}) }}">show</a>
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
<a class="btn btn-sm btn-outline-primary" href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}">edit</a>
|
||||||
{% for food_vendor in food_vendors %}
|
</td>
|
||||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
</tr>
|
||||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 dark:text-gray-100 sm:pl-6">{{ food_vendor.name }}</td>
|
{% else %}
|
||||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
<tr>
|
||||||
<a href="{{ path('app_food_vendor_show', {'id': food_vendor.id}) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 mr-2">View</a>
|
<td colspan="3" class="text-center text-muted">no records found</td>
|
||||||
<a href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">Edit</a>
|
</tr>
|
||||||
</td>
|
{% endfor %}
|
||||||
</tr>
|
</tbody>
|
||||||
{% else %}
|
</table>
|
||||||
<tr>
|
|
||||||
<td colspan="2" class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-500 dark:text-gray-400 sm:pl-6">No vendors found</td>
|
<a class="btn btn-primary" href="{{ path('app_food_vendor_new') }}">Create new</a>
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
{% block title %}New FoodVendor{% endblock %}
|
{% block title %}New FoodVendor{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>Create new FoodVendor</h1>
|
<h1 class="mb-4">Create new FoodVendor</h1>
|
||||||
|
|
||||||
{{ include('food_vendor/_form.html.twig') }}
|
<div class="mb-4">
|
||||||
|
{{ include('food_vendor/_form.html.twig') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="{{ path('app_food_vendor_index') }}">back to list</a>
|
<a class="btn btn-secondary" href="{{ path('app_food_vendor_index') }}">back to list</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
{% block title %}FoodVendor{% endblock %}
|
{% block title %}FoodVendor{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>FoodVendor</h1>
|
<h1 class="mb-4">FoodVendor</h1>
|
||||||
|
|
||||||
<table class="table">
|
<table class="table table-bordered w-auto">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
|
@ -18,23 +18,22 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<section>
|
<section class="mb-4">
|
||||||
<h2>known menuitems</h2>
|
<h2>known menuitems</h2>
|
||||||
<ul>
|
<ul class="list-group list-group-flush">
|
||||||
{% for item in food_vendor.menuItems %}
|
{% for item in food_vendor.menuItems %}
|
||||||
<li>
|
<li class="list-group-item">
|
||||||
<a href="{{ path('app_menu_item_show', {'id': item.id}) }}">{{ item.name }}</a>
|
<a href="{{ path('app_menu_item_show', {'id': item.id}) }}">{{ item.name }}</a>
|
||||||
{% if(item.aliasOf) %}
|
{% if(item.aliasOf) %}
|
||||||
(alias of: {{ item.aliasOf.name }})
|
<span class="text-muted">(alias of: {{ item.aliasOf.name }})</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
<a href="{{ path('app_food_vendor_index') }}">back to list</a>
|
<a class="btn btn-secondary" href="{{ path('app_food_vendor_index') }}">back to list</a>
|
||||||
|
<a class="btn btn-primary" href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}">edit</a>
|
||||||
<a href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}">edit</a>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<form method="post" action="{{ path('app_menu_item_delete', {'id': menu_item.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
|
<form method="post" action="{{ path('app_menu_item_delete', {'id': menu_item.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
|
||||||
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ menu_item.id) }}">
|
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ menu_item.id) }}">
|
||||||
<button class="btn">Delete</button>
|
<button class="btn btn-danger btn-sm">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{{ form_start(form) }}
|
{{ form_start(form, {'attr': {'class': 'mb-3'}}) }}
|
||||||
{{ form_widget(form) }}
|
{{ form_widget(form, {'attr': {'class': 'form-control'}}) }}
|
||||||
<button class="btn">{{ button_label|default('Save') }}</button>
|
<button class="btn btn-primary mt-2">{{ button_label|default('Save') }}</button>
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
|
@ -3,9 +3,13 @@
|
||||||
{% block title %}Edit MenuItem{% endblock %}
|
{% block title %}Edit MenuItem{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>Edit MenuItem</h1>
|
<h1 class="mb-4">Edit MenuItem</h1>
|
||||||
|
|
||||||
{{ include('menu_item/_form.html.twig', {'button_label': 'Update'}) }}
|
<div class="mb-4">
|
||||||
|
{{ include('menu_item/_form.html.twig', {'button_label': 'Update'}) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
{{ include('menu_item/_delete_form.html.twig') }}
|
<div class="d-flex gap-2">
|
||||||
|
{{ include('menu_item/_delete_form.html.twig') }}
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
{% block title %}MenuItem index{% endblock %}
|
{% block title %}MenuItem index{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>MenuItem index</h1>
|
<h1 class="mb-4">MenuItem index</h1>
|
||||||
|
|
||||||
<table class="table">
|
<table class="table table-striped table-hover">
|
||||||
<thead>
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Id</th>
|
<th>Id</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
|
@ -19,17 +19,17 @@
|
||||||
<td>{{ menu_item.id }}</td>
|
<td>{{ menu_item.id }}</td>
|
||||||
<td>{{ menu_item.name }}</td>
|
<td>{{ menu_item.name }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ path('app_menu_item_show', {'id': menu_item.id}) }}">show</a>
|
<a class="btn btn-sm btn-outline-info me-1" href="{{ path('app_menu_item_show', {'id': menu_item.id}) }}">show</a>
|
||||||
<a href="{{ path('app_menu_item_edit', {'id': menu_item.id}) }}">edit</a>
|
<a class="btn btn-sm btn-outline-primary" href="{{ path('app_menu_item_edit', {'id': menu_item.id}) }}">edit</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3">no records found</td>
|
<td colspan="3" class="text-center text-muted">no records found</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<a href="{{ path('app_menu_item_new') }}">Create new</a>
|
<a class="btn btn-primary" href="{{ path('app_menu_item_new') }}">Create new</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
{% block title %}New MenuItem{% endblock %}
|
{% block title %}New MenuItem{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>Create new MenuItem</h1>
|
<h1 class="mb-4">Create new MenuItem</h1>
|
||||||
|
|
||||||
{{ include('menu_item/_form.html.twig') }}
|
<div class="mb-4">
|
||||||
|
{{ include('menu_item/_form.html.twig') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="{{ path('app_menu_item_index') }}">back to list</a>
|
<a class="btn btn-secondary" href="{{ path('app_menu_item_index') }}">back to list</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
{% block title %}MenuItem{% endblock %}
|
{% block title %}MenuItem{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>MenuItem</h1>
|
<h1 class="mb-4">MenuItem</h1>
|
||||||
|
|
||||||
<table class="table">
|
<table class="table table-bordered w-auto">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Id</th>
|
<th>Id</th>
|
||||||
|
@ -25,9 +25,9 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>Aliases</th>
|
<th>Aliases</th>
|
||||||
<td>
|
<td>
|
||||||
<ul>
|
<ul class="list-group list-group-flush">
|
||||||
{% for alias in menu_item.aliases %}
|
{% for alias in menu_item.aliases %}
|
||||||
<li>{{ alias.name }}</li>
|
<li class="list-group-item">{{ alias.name }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
|
@ -36,9 +36,9 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<a href="{{ path('app_food_vendor_show', { 'id': menu_item.foodVendor.id}) }}">back to list</a>
|
<div class="d-flex gap-2">
|
||||||
|
<a class="btn btn-secondary" href="{{ path('app_food_vendor_show', { 'id': menu_item.foodVendor.id}) }}">back to list</a>
|
||||||
<a href="{{ path('app_menu_item_edit', {'id': menu_item.id}) }}">edit</a>
|
<a class="btn btn-primary" href="{{ path('app_menu_item_edit', {'id': menu_item.id}) }}">edit</a>
|
||||||
|
{{ include('menu_item/_delete_form.html.twig') }}
|
||||||
{{ include('menu_item/_delete_form.html.twig') }}
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{{ form_start(form) }}
|
{{ form_start(form, {'attr': {'class': 'mb-3'}}) }}
|
||||||
{{ form_widget(form) }}
|
{{ form_widget(form, {'attr': {'class': 'form-control'}}) }}
|
||||||
<button class="btn">{{ button_label|default('Save') }}</button>
|
<button class="btn btn-primary mt-2">{{ button_label|default('Save') }}</button>
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
{% block title %}Edit OrderItem{% endblock %}
|
{% block title %}Edit OrderItem{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>Edit OrderItem</h1>
|
<h1 class="mb-4">Edit OrderItem</h1>
|
||||||
|
|
||||||
{{ include('order_item/_form.html.twig', {'button_label': 'Update'}) }}
|
<div class="mb-4">
|
||||||
|
{{ include('order_item/_form.html.twig', {'button_label': 'Update'}) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="{{ path('app_food_order_show', {'id': order_item.foodOrder.id}) }}">back to list</a>
|
<a class="btn btn-secondary" href="{{ path('app_food_order_show', {'id': order_item.foodOrder.id}) }}">back to list</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -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') }}
|
<div class="mb-4">
|
||||||
|
{{ include('order_item/_form.html.twig') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
{% if food_order.foodVendor.menuLink != '' %}
|
{% if food_order.foodVendor.menuLink != '' %}
|
||||||
<a href="{{ food_order.foodVendor.menuLink }}" target="_blank">
|
<a class="btn btn-outline-secondary mb-3" href="{{ food_order.foodVendor.menuLink }}" target="_blank">
|
||||||
External link to Menu
|
External link to Menu
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div>
|
<div class="mb-2">
|
||||||
<b>click a button to select a given menuitem</b>
|
<b>click a button to select a given menuitem</b>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="mb-3 d-flex flex-wrap gap-2">
|
||||||
{% for menuItem in menuItems %}
|
{% for menuItem in menuItems %}
|
||||||
<a href="#" data-menu-item>{{ menuItem.name }}</a>
|
<a class="btn btn-outline-info btn-sm" href="#" data-menu-item>{{ menuItem.name }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<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}) }}">back to list</a>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.querySelectorAll('[data-menu-item]').forEach(function(element) {
|
document.querySelectorAll('[data-menu-item]').forEach(function(element) {
|
||||||
|
|
|
@ -2,15 +2,10 @@
|
||||||
|
|
||||||
{% block title %}Tell me your name{% endblock %}
|
{% block title %}Tell me your name{% endblock %}
|
||||||
|
|
||||||
{% block header %}Tell me your name{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
<h1 class="mb-4">Tell me your name</h1>
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<p class="mb-3">By submitting the form, you agree that your username will be stored as a cookie.</p>
|
||||||
<div class="max-w-xl text-sm text-gray-500 dark:text-gray-400 mb-6">
|
<div class="mb-4">
|
||||||
<p>By submitting the form, you agree that your username will be stored as a cookie.</p>
|
{{ include('_form.html.twig') }}
|
||||||
</div>
|
|
||||||
{{ include('_form.html.twig') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,26 +0,0 @@
|
||||||
<?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]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
<?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');
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -44,7 +44,7 @@ describe(FoodOrderController::class, function (): void {
|
||||||
|
|
||||||
$crawler = $this->client->request('GET', "{$this->path}list");
|
$crawler = $this->client->request('GET', "{$this->path}list");
|
||||||
$this->assertResponseStatusCodeSame(200);
|
$this->assertResponseStatusCodeSame(200);
|
||||||
$this->assertPageTitleContains('Food Orders');
|
$this->assertPageTitleContains('FoodOrder index');
|
||||||
$this->assertCount(
|
$this->assertCount(
|
||||||
1,
|
1,
|
||||||
$crawler->filter('td')
|
$crawler->filter('td')
|
||||||
|
@ -99,29 +99,17 @@ describe(FoodOrderController::class, function (): void {
|
||||||
|
|
||||||
$crawler = $this->client->request('GET', "{$this->path}{$order->getId()}");
|
$crawler = $this->client->request('GET', "{$this->path}{$order->getId()}");
|
||||||
$this->assertResponseIsSuccessful();
|
$this->assertResponseIsSuccessful();
|
||||||
|
$tdContent = $crawler->filter(
|
||||||
// Find all tables and get the last one (order items table)
|
'table.table-hover tbody tr:nth-child(1) td:nth-child(3)'
|
||||||
$tables = $crawler->filter('table');
|
)->text();
|
||||||
$lastTable = $tables->eq($tables->count() - 1);
|
|
||||||
|
|
||||||
// Get the item names from the table rows
|
|
||||||
$rows = $lastTable->filter('tbody tr');
|
|
||||||
$tdContent = $rows->eq(0)
|
|
||||||
->filter('td')
|
|
||||||
->eq(2)
|
|
||||||
->text();
|
|
||||||
$this->assertEquals('A', $tdContent);
|
$this->assertEquals('A', $tdContent);
|
||||||
|
$tdContent = $crawler->filter(
|
||||||
$tdContent = $rows->eq(1)
|
'table.table-hover tbody tr:nth-child(2) td:nth-child(3)'
|
||||||
->filter('td')
|
)->text();
|
||||||
->eq(2)
|
|
||||||
->text();
|
|
||||||
$this->assertEquals('B', $tdContent);
|
$this->assertEquals('B', $tdContent);
|
||||||
|
$tdContent = $crawler->filter(
|
||||||
$tdContent = $rows->eq(2)
|
'table.table-hover tbody tr:nth-child(3) td:nth-child(3)'
|
||||||
->filter('td')
|
)->text();
|
||||||
->eq(2)
|
|
||||||
->text();
|
|
||||||
$this->assertEquals('C', $tdContent);
|
$this->assertEquals('C', $tdContent);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -136,12 +124,12 @@ describe(FoodOrderController::class, function (): void {
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
$crawler = $this->client->request('GET', "{$this->path}list");
|
$crawler = $this->client->request('GET', "{$this->path}list");
|
||||||
$this->assertResponseStatusCodeSame(200);
|
$this->assertResponseStatusCodeSame(200);
|
||||||
$this->assertPageTitleContains('Food Orders');
|
$this->assertPageTitleContains('FoodOrder index');
|
||||||
$this->assertElementContainsCount(
|
$this->assertElementContainsCount(
|
||||||
$crawler,
|
$crawler,
|
||||||
'td',
|
'td',
|
||||||
1,
|
1,
|
||||||
'for older orders'
|
'older orders'
|
||||||
);
|
);
|
||||||
$this->assertElementContainsCount(
|
$this->assertElementContainsCount(
|
||||||
$crawler,
|
$crawler,
|
||||||
|
@ -151,7 +139,7 @@ describe(FoodOrderController::class, function (): void {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('paginatedFirstPage', function (int $page, int $prevPage, int $nextPage, int $items = 20): void {
|
test('paginatedFirstPage', function (int $page, int $prevPage, int $nextPage, int $items = 10): void {
|
||||||
foreach (range(1, 35) as $i) {
|
foreach (range(1, 35) as $i) {
|
||||||
$order = new FoodOrder($this->generateOldUlid());
|
$order = new FoodOrder($this->generateOldUlid());
|
||||||
$order->setFoodVendor($this->vendor);
|
$order->setFoodVendor($this->vendor);
|
||||||
|
@ -162,7 +150,7 @@ describe(FoodOrderController::class, function (): void {
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
$crawler = $this->client->request('GET', "{$this->path}list/archive/{$page}");
|
$crawler = $this->client->request('GET', "{$this->path}list/archive/{$page}");
|
||||||
$this->assertResponseStatusCodeSame(200);
|
$this->assertResponseStatusCodeSame(200);
|
||||||
$this->assertPageTitleContains('Food Orders');
|
$this->assertPageTitleContains('FoodOrder index');
|
||||||
$this->assertElementContainsCount(
|
$this->assertElementContainsCount(
|
||||||
$crawler,
|
$crawler,
|
||||||
'td',
|
'td',
|
||||||
|
@ -172,14 +160,14 @@ describe(FoodOrderController::class, function (): void {
|
||||||
if ($prevPage > 0) {
|
if ($prevPage > 0) {
|
||||||
$prevPage = $prevPage === 1 ? '' : "/{$prevPage}";
|
$prevPage = $prevPage === 1 ? '' : "/{$prevPage}";
|
||||||
$node = $crawler->filter('a')
|
$node = $crawler->filter('a')
|
||||||
->reduce(static fn(Crawler $node, $i): bool => $node->text() === 'Previous Page')
|
->reduce(static fn(Crawler $node, $i): bool => $node->text() === 'previous page')
|
||||||
->first();
|
->first();
|
||||||
$target = $node->attr('href');
|
$target = $node->attr('href');
|
||||||
$this->assertTrue(str_ends_with((string) $target, $prevPage));
|
$this->assertTrue(str_ends_with((string) $target, $prevPage));
|
||||||
}
|
}
|
||||||
if ($prevPage > 3) {
|
if ($prevPage > 3) {
|
||||||
$node = $crawler->filter('a')
|
$node = $crawler->filter('a')
|
||||||
->reduce(static fn(Crawler $node, $i): bool => $node->text() === 'Next Page')
|
->reduce(static fn(Crawler $node, $i): bool => $node->text() === 'next page')
|
||||||
->first();
|
->first();
|
||||||
$target = $node->attr('href');
|
$target = $node->attr('href');
|
||||||
$this->assertTrue(str_ends_with((string) $target, "/{$nextPage}"));
|
$this->assertTrue(str_ends_with((string) $target, "/{$nextPage}"));
|
||||||
|
@ -190,7 +178,7 @@ describe(FoodOrderController::class, function (): void {
|
||||||
[1, 0, 2],
|
[1, 0, 2],
|
||||||
[2, 1, 3],
|
[2, 1, 3],
|
||||||
[3, 2, 4],
|
[3, 2, 4],
|
||||||
[4, 3, 0, 10],
|
[4, 3, 0, 5],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -242,7 +230,6 @@ describe(FoodOrderController::class, function (): void {
|
||||||
$openOrder = $this->repository->find($order->getId());
|
$openOrder = $this->repository->find($order->getId());
|
||||||
$this->assertTrue($openOrder->isClosed());
|
$this->assertTrue($openOrder->isClosed());
|
||||||
});
|
});
|
||||||
|
|
||||||
})
|
})
|
||||||
->covers(
|
->covers(
|
||||||
FoodOrderController::class,
|
FoodOrderController::class,
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe(FoodVendorController::class, function (): void {
|
||||||
$this->client->request('GET', $this->path);
|
$this->client->request('GET', $this->path);
|
||||||
|
|
||||||
$this->assertResponseStatusCodeSame(200);
|
$this->assertResponseStatusCodeSame(200);
|
||||||
$this->assertPageTitleContains('Food Vendors');
|
$this->assertPageTitleContains('FoodVendor index');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('new', function (): void {
|
test('new', function (): void {
|
||||||
|
@ -119,7 +119,7 @@ describe(FoodVendorController::class, function (): void {
|
||||||
)->text();
|
)->text();
|
||||||
$this->assertSame('My Title', $nameNode);
|
$this->assertSame('My Title', $nameNode);
|
||||||
|
|
||||||
$itemNodes = $crawler->filter('li');
|
$itemNodes = $crawler->filter('ul.list-group li.list-group-item');
|
||||||
|
|
||||||
$this->assertCount(4, $itemNodes);
|
$this->assertCount(4, $itemNodes);
|
||||||
});
|
});
|
||||||
|
|
|
@ -136,9 +136,10 @@ describe(MenuItemController::class, function (): void {
|
||||||
$this->assertTrue($menuItem->isDeleted());
|
$this->assertTrue($menuItem->isDeleted());
|
||||||
|
|
||||||
$crawler = $this->client->request('GET', '/order/item/new/' . $order->getId());
|
$crawler = $this->client->request('GET', '/order/item/new/' . $order->getId());
|
||||||
$count = $crawler->filter('form')
|
$count = $crawler->filter('body > main:nth-child(2) > div:nth-child(5)')
|
||||||
|
->children()
|
||||||
->count();
|
->count();
|
||||||
$this->assertSame(1, $count);
|
$this->assertSame(2, $count);
|
||||||
|
|
||||||
$this->assertResponseIsSuccessful();
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,8 @@ describe(OrderItemController::class, function (): void {
|
||||||
sprintf('%snew/%s', $this->path, $this->order->getId())
|
sprintf('%snew/%s', $this->path, $this->order->getId())
|
||||||
);
|
);
|
||||||
|
|
||||||
$children = $crawler->filter('form');
|
$children = $crawler->filter('body > main:nth-child(2) > div:nth-child(5)')
|
||||||
|
->children();
|
||||||
|
|
||||||
$this->assertCount(1, $children);
|
$this->assertCount(1, $children);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use App\Tests\DbApiTestCase;
|
|
||||||
use App\Tests\DbWebTest;
|
use App\Tests\DbWebTest;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -16,8 +15,6 @@ 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');
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Tests\Unit\Entity;
|
||||||
use App\Entity\FoodOrder;
|
use App\Entity\FoodOrder;
|
||||||
use App\Entity\FoodVendor;
|
use App\Entity\FoodVendor;
|
||||||
use App\Entity\MenuItem;
|
use App\Entity\MenuItem;
|
||||||
|
use InvalidArgumentException;
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
use function describe;
|
use function describe;
|
||||||
|
@ -20,6 +21,17 @@ describe(FoodVendor::class, function (): void {
|
||||||
$vendor->setPhone('1234567890');
|
$vendor->setPhone('1234567890');
|
||||||
$this->assertEquals('1234567890', $vendor->getPhone());
|
$this->assertEquals('1234567890', $vendor->getPhone());
|
||||||
|
|
||||||
|
// Test emojis field
|
||||||
|
$this->assertNull($vendor->getEmojis());
|
||||||
|
$emojis = '😀😂🎉👍❤️';
|
||||||
|
$vendor->setEmojis($emojis);
|
||||||
|
$this->assertEquals($emojis, $vendor->getEmojis());
|
||||||
|
|
||||||
|
// Test emojis validation
|
||||||
|
$tooManyEmojis = '😀😂🎉👍❤️🚀🎈🎁🎊🎋🎍🎎🎏🎐🎑🎒🎓🎔🎕🎖🎗🎘🎙🎚🎛🎜🎝🎞🎟🎠🎡🎢';
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
$vendor->setEmojis($tooManyEmojis);
|
||||||
|
|
||||||
$this->assertCount(0, $vendor->getFoodOrders());
|
$this->assertCount(0, $vendor->getFoodOrders());
|
||||||
$order1 = new FoodOrder;
|
$order1 = new FoodOrder;
|
||||||
$vendor->addFoodOrder($order1);
|
$vendor->addFoodOrder($order1);
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
test('example test', function (): void {
|
|
||||||
// This is a simple example test
|
|
||||||
expect(true)
|
|
||||||
->toBeTrue();
|
|
||||||
expect(1 + 1)
|
|
||||||
->toBe(2);
|
|
||||||
expect('hello world')
|
|
||||||
->toContain('world');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('array operations', function (): void {
|
|
||||||
$array = [1, 2, 3];
|
|
||||||
|
|
||||||
expect($array)
|
|
||||||
->toHaveCount(3);
|
|
||||||
expect($array)
|
|
||||||
->toContain(2);
|
|
||||||
expect($array[0])->toBe(1);
|
|
||||||
});
|
|
Loading…
Add table
Add a link
Reference in a new issue