diff --git a/assets/app.js b/assets/app.js index 0bb46da..b98a7ee 100644 --- a/assets/app.js +++ b/assets/app.js @@ -11,57 +11,24 @@ import './styles/app.scss' import $ from 'jquery' import 'bootstrap' -// Dropzone stuff move to component -import {Dropzone} from 'dropzone' - -// TODO handle error (Chapter 26) -const formElement = $('#dropzoneForm') -if (formElement) { - const previewContent = $('#preview-content').html() - console.log(previewContent) - const dropzone = new Dropzone('#dropzoneForm', { - acceptedFiles: '.jpg, .jpeg, .png', - maxFiles: 1, - init: function () { - this.hiddenFileInput.removeAttribute('multiple') - this.on('maxfilesexceeded', (file) => { - this.removeAllFiles() - this.addFile(file) - }) - this.on('error', (file, data) => { - console.log('error'); - if (data.detail) { - this.emit('error', file, data.detail) - } - - }) - } - }) - console.log('filename', previewContent) - const mockFile = { name: previewContent } - console.log('file', mockFile) - dropzone.displayExistingFile(mockFile) -} - -// End Dropzone stuff move to component +// start the Stimulus application +import './bootstrap' //import './js/index' + // needed for legacy code //global.$ = $ if (window.matchMedia('(prefers-color-scheme)').media !== 'not all') { - console.log('🎉 Dark mode is supported') + console.log('🎉 Dark mode is supported') } $(document).ready(() => { - console.log('ready') - $('#toggleSidebar').on('click', () => { - const toggleIcon = $('#toggleIcon') - toggleIcon.toggleClass('fa fa-lg fa-fw fa-caret-square-o-left') - toggleIcon.toggleClass('fa fa-lg fa-fw fa-caret-square-o-right') - $('#sidebar').toggleClass('active') - }) + console.log('ready') + $('#toggleSidebar').on('click', () => { + const toggleIcon = $('#toggleIcon') + toggleIcon.toggleClass('fa fa-lg fa-fw fa-caret-square-o-left') + toggleIcon.toggleClass('fa fa-lg fa-fw fa-caret-square-o-right') + $('#sidebar').toggleClass('active') + }) }) - -// start the Stimulus application -//import './bootstrap' diff --git a/assets/controllers.json b/assets/controllers.json index a1c6e90..1620b19 100644 --- a/assets/controllers.json +++ b/assets/controllers.json @@ -1,4 +1,30 @@ { - "controllers": [], + "controllers": { + "@symfony/ux-cropperjs": { + "cropper": { + "enabled": true, + "fetch": "eager", + "autoimport": { + "cropperjs/dist/cropper.min.css": true, + "@symfony/ux-cropperjs/src/style.css": true + } + } + }, + "@symfony/ux-dropzone": { + "dropzone": { + "enabled": true, + "fetch": "eager", + "autoimport": { + "@symfony/ux-dropzone/src/style.css": true + } + } + }, + "@symfony/ux-turbo": { + "turbo-core": { + "enabled": true, + "fetch": "eager" + } + } + }, "entrypoints": [] } diff --git a/assets/controllers/hello_controller.js b/assets/controllers/hello_controller.js deleted file mode 100644 index e847027..0000000 --- a/assets/controllers/hello_controller.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Controller } from '@hotwired/stimulus'; - -/* - * This is an example Stimulus controller! - * - * Any element with a data-controller="hello" attribute will cause - * this controller to be executed. The name "hello" comes from the filename: - * hello_controller.js -> "hello" - * - * Delete this file or adapt it for your use! - */ -export default class extends Controller { - connect() { - this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js'; - } -} diff --git a/assets/controllers/upload-avatar_controller.js b/assets/controllers/upload-avatar_controller.js new file mode 100644 index 0000000..81206cb --- /dev/null +++ b/assets/controllers/upload-avatar_controller.js @@ -0,0 +1,125 @@ +import { Controller } from '@hotwired/stimulus' +import { Dropzone } from 'dropzone' +import Cropper from 'cropperjs' + +export default class extends Controller { + static values = { + avatarImage: String, + userId: Number + } + + connect() { + let avatarDropzone = new Dropzone('#avatarDropzone', { + url: `/user/upload/avatar/${this.userIdValue}`, + avatarUrl: this.avatarImageValue, + acceptedFiles: '.jpg, .jpeg, .png, gif', + maxFiles: 1, + dictDefaultMessage: '', + init() { + avatarDropzone = this + // If the thumbnail is already in the right size on your server: + const mockFile = { name: 'Filename', size: 12345 } + const callback = null // Optional callback when it's done + const crossOrigin = null // Added to the `img` tag for crossOrigin handling + const resizeThumbnail = false // Tells Dropzone whether it should resize the image first + avatarDropzone.displayExistingFile( + mockFile, + avatarDropzone.options.avatarUrl, + callback, + crossOrigin, + resizeThumbnail + ) + avatarDropzone.files.push(mockFile) // line missing in official docs + }, + transformFile(file, done) { + // Create the image editor overlay + const editor = document.createElement('div') + editor.style.position = 'fixed' + editor.style.left = '25px' + editor.style.right = '25px' + editor.style.top = '25px' + editor.style.bottom = '25px' + editor.style.zIndex = '9999' + editor.style.borderRadius = '15px;' + editor.style.borderColor = '#FF8844' + editor.style.backgroundColor = '#000' + document.body.appendChild(editor) + + // Create an image node for Cropper.js + const image = new Image() + image.src = URL.createObjectURL(file) + editor.appendChild(image) + + // Create Cropper.js + const cropper = new Cropper(image, { aspectRatio: 1 }) + + // Create confirm button at the top left of the viewport + const buttonCrop = document.createElement('button') + buttonCrop.style.position = 'absolute' + buttonCrop.style.right = '10px' + buttonCrop.style.bottom = '50px' + buttonCrop.style.zIndex = '9999' + buttonCrop.textContent = 'Crop' + buttonCrop.className = 'btn btn-secondary' + editor.appendChild(buttonCrop) + buttonCrop.addEventListener('click', () => { + // clear preview + //formElement.style.backgroundImage = '' + + const canvas = cropper.getCroppedCanvas({ + width: 256, + height: 256 + }) + canvas.toBlob((blob) => { + avatarDropzone.createThumbnail( + blob, + avatarDropzone.options.thumbnailWidth, + avatarDropzone.options.thumbnailHeight, + avatarDropzone.options.thumbnailMethod, + false, + (dataURL) => { + avatarDropzone.emit('thumbnail', file, dataURL) + done(blob) + } + ) + }) + + // Remove the editor from the view + document.body.removeChild(editor) + }) + + // Create keep original button + const buttonKeep = document.createElement('button') + buttonKeep.style.position = 'absolute' + buttonKeep.style.right = '100px' + buttonKeep.style.bottom = '50px' + buttonKeep.style.zIndex = '9999' + buttonKeep.textContent = 'Keep Original' + buttonKeep.className = 'btn btn-primary' + buttonKeep.focus() + editor.appendChild(buttonKeep) + buttonKeep.addEventListener('click', () => { + //formElement.style.backgroundImage = '' + + // Remove the editor from the view + this.removeAllFiles() + + done(file) + document.body.removeChild(editor) + }) + + // info text + const infoText = document.createElement('span') + infoText.style.position = 'absolute' + infoText.style.opacity = '0.5' + infoText.style.left = '100px' + infoText.style.bottom = '50px' + infoText.style.display = 'inline:block' + infoText.style.zIndex = '9999' + infoText.textContent = 'Use mouse wheel/touchpad to adapt size' + infoText.className = 'btn btn-secondary' + editor.appendChild(infoText) + } + }) + } +} diff --git a/assets/styles/app.scss b/assets/styles/app.scss index fbf9f2f..1319010 100644 --- a/assets/styles/app.scss +++ b/assets/styles/app.scss @@ -27,9 +27,9 @@ $list-group-hover-bg: darken($list-group, 10%); $list-group-active-bg: $list-group; $list-group-action-active-bg: $list-group; -@import 'bootstrap/scss/bootstrap'; +@import '~bootstrap'; @import 'dropzone/dist/dropzone'; - +@import 'cropperjs'; @import './components/sidebar'; @@ -40,19 +40,35 @@ html, body { background: #0e0e10; } -// Dropzone -.dropzone .dz-preview.dz-image-preview { +// Dropzone +.dropzone .dz-preview { + background: transparent !important; + margin: 0 !important; +} + +.dropzone-container { + width: 160px; + height: 160px; +} + +.dropzone-preview-filename { + word-wrap: anywhere; + display: none; +} + +.dropzone-preview-image { + flex-basis: 0; + min-width: 150px; + max-width: 150px; + min-height: 150px; + max-height: 150px; +} + +.dz-image { background: transparent !important; } -.article-author-img { - width: 80px; - margin: 15px; -} - -/// - #main_content { overflow-y: auto; diff --git a/composer.json b/composer.json index b632310..c380b50 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,8 @@ "league/commonmark": "^2.3", "liip/imagine-bundle": "^2.9", "nelmio/cors-bundle": "^2.2", + "php-flasher/flasher-sweetalert-symfony": "^1.7", + "php-flasher/flasher-symfony": "^1.7", "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpdoc-parser": "^1.4", "rector/rector": "^0.12.21", @@ -48,6 +50,9 @@ "symfony/string": "6.1.*", "symfony/translation": "6.1.*", "symfony/twig-bundle": "6.1.*", + "symfony/ux-cropperjs": "^2.5", + "symfony/ux-dropzone": "^2.5", + "symfony/ux-turbo": "^2.5", "symfony/validator": "6.1.*", "symfony/web-link": "6.1.*", "symfony/webapp-meta": "^1.0", @@ -57,7 +62,8 @@ "symfonycasts/verify-email-bundle": "^1.12", "twig/extra-bundle": "^3.4", "twig/markdown-extra": "^3.4", - "twig/twig": "3.4.*" + "twig/twig": "3.4.*", + "ext-http": "*" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index ccaa754..e4ad933 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1c940d128112ec366d0f81bc6eccee45", + "content-hash": "e475387f74f1428bb35d9943950aa2f8", "packages": [ { "name": "dflydev/dot-access-data", @@ -1689,6 +1689,125 @@ ], "time": "2022-10-17T19:48:16+00:00" }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "67c26b443f348a51926030c83481b85718457d3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", + "reference": "67c26b443f348a51926030c83481b85718457d3d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2022-10-26T14:07:24+00:00" + }, { "name": "imagine/imagine", "version": "1.3.2", @@ -1751,6 +1870,90 @@ }, "time": "2022-04-01T11:58:30+00:00" }, + { + "name": "intervention/image", + "version": "2.7.2", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image.git", + "reference": "04be355f8d6734c826045d02a1079ad658322dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad", + "reference": "04be355f8d6734c826045d02a1079ad658322dad", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1 || ^2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.2", + "phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15" + }, + "suggest": { + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + } + } + }, + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@intervention.io", + "homepage": "https://intervention.io/" + } + ], + "description": "Image handling and manipulation library with support for Laravel integration", + "homepage": "http://image.intervention.io/", + "keywords": [ + "gd", + "image", + "imagick", + "laravel", + "thumbnail", + "watermark" + ], + "support": { + "issues": "https://github.com/Intervention/image/issues", + "source": "https://github.com/Intervention/image/tree/2.7.2" + }, + "funding": [ + { + "url": "https://paypal.me/interventionio", + "type": "custom" + }, + { + "url": "https://github.com/Intervention", + "type": "github" + } + ], + "time": "2022-05-21T17:30:32+00:00" + }, { "name": "knplabs/knp-time-bundle", "version": "v1.20.0", @@ -2489,6 +2692,316 @@ }, "time": "2022-09-12T23:36:20+00:00" }, + { + "name": "php-flasher/flasher", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/php-flasher/flasher.git", + "reference": "10071abc73d0311a99e1860e88d5895ac4d7535c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-flasher/flasher/zipball/10071abc73d0311a99e1860e88d5895ac4d7535c", + "reference": "10071abc73d0311a99e1860e88d5895ac4d7535c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Flasher\\Prime\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Younes KHOUBZA", + "email": "younes.khoubza@gmail.com", + "homepage": "https://www.linkedin.com/in/younes-khoubza", + "role": "Developer" + } + ], + "description": "A powerful and flexible flash notification system for PHP", + "homepage": "https://php-flasher.io", + "keywords": [ + "Flasher", + "alerts", + "flash", + "laravel", + "lumen", + "messages", + "notifications", + "notify", + "noty", + "notyf", + "php", + "pnotify", + "sweet alert", + "symfony", + "toastr" + ], + "support": { + "source": "https://github.com/php-flasher/flasher/tree/v1.7.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/yoeunes", + "type": "custom" + }, + { + "url": "https://ko-fi.com/yoeunes", + "type": "ko_fi" + }, + { + "url": "https://opencollective.com/php-flasher", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/yoeunes", + "type": "patreon" + } + ], + "time": "2022-07-05T19:33:26+00:00" + }, + { + "name": "php-flasher/flasher-sweetalert", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/php-flasher/flasher-sweetalert.git", + "reference": "abab36c6a70405ad9838238338a76853541baa34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-flasher/flasher-sweetalert/zipball/abab36c6a70405ad9838238338a76853541baa34", + "reference": "abab36c6a70405ad9838238338a76853541baa34", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "php-flasher/flasher": "^1.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Flasher\\SweetAlert\\Prime\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Younes KHOUBZA", + "email": "younes.khoubza@gmail.com", + "homepage": "https://www.linkedin.com/in/younes-khoubza", + "role": "Developer" + } + ], + "description": "PHP Flasher adapter for Sweet Alert 2", + "homepage": "https://php-flasher.io", + "keywords": [ + "alerts", + "laravel", + "lumen", + "messages", + "notifications", + "notify", + "php", + "pnotify", + "sweetalert", + "symfony" + ], + "support": { + "source": "https://github.com/php-flasher/flasher-sweetalert/tree/v1.7.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/yoeunes", + "type": "custom" + }, + { + "url": "https://ko-fi.com/yoeunes", + "type": "ko_fi" + }, + { + "url": "https://opencollective.com/php-flasher", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/yoeunes", + "type": "patreon" + } + ], + "time": "2022-07-05T19:36:36+00:00" + }, + { + "name": "php-flasher/flasher-sweetalert-symfony", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/php-flasher/flasher-sweetalert-symfony.git", + "reference": "f8f10ad9760b30c8fc1fe4e9891b595a5cd8c51e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-flasher/flasher-sweetalert-symfony/zipball/f8f10ad9760b30c8fc1fe4e9891b595a5cd8c51e", + "reference": "f8f10ad9760b30c8fc1fe4e9891b595a5cd8c51e", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "php-flasher/flasher-sweetalert": "^1.7", + "php-flasher/flasher-symfony": "^1.7" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Flasher\\SweetAlert\\Symfony\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Younes KHOUBZA", + "email": "younes.khoubza@gmail.com", + "homepage": "https://www.linkedin.com/in/younes-khoubza", + "role": "Developer" + } + ], + "description": "PHP Flasher Symfony adapter for Sweet Alert 2", + "homepage": "https://php-flasher.io", + "keywords": [ + "alerts", + "bundle", + "flex", + "laravel", + "lumen", + "messages", + "notifications", + "notify", + "php", + "pnotify", + "sweetalert", + "symfony", + "toastr", + "yoeunes" + ], + "support": { + "source": "https://github.com/php-flasher/flasher-sweetalert-symfony/tree/v1.7.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/yoeunes", + "type": "custom" + }, + { + "url": "https://ko-fi.com/yoeunes", + "type": "ko_fi" + }, + { + "url": "https://opencollective.com/php-flasher", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/yoeunes", + "type": "patreon" + } + ], + "time": "2022-07-05T19:36:36+00:00" + }, + { + "name": "php-flasher/flasher-symfony", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/php-flasher/flasher-symfony.git", + "reference": "1e003819402f415d07b8fedabfff527bd164822b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-flasher/flasher-symfony/zipball/1e003819402f415d07b8fedabfff527bd164822b", + "reference": "1e003819402f415d07b8fedabfff527bd164822b", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "php-flasher/flasher": "^1.7", + "symfony/config": "^2.0|^3.0|^4.0|^5.0|^6.0", + "symfony/console": "^2.0|^3.0|^4.0|^5.0|^6.0", + "symfony/dependency-injection": "^2.0|^3.0|^4.0|^5.0|^6.0", + "symfony/http-kernel": "^2.0|^3.0|^4.0|^5.0|^6.0", + "symfony/translation": "^2.0|^3.0|^4.0|^5.0|^6.0", + "symfony/twig-bundle": "^2.0|^3.0|^4.0|^5.0|^6.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Flasher\\Symfony\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Younes KHOUBZA", + "email": "younes.khoubza@gmail.com", + "homepage": "https://www.linkedin.com/in/younes-khoubza", + "role": "Developer" + } + ], + "description": "Symfony bundle integrating PHP Flasher into Symfony applications", + "homepage": "https://php-flasher.io", + "keywords": [ + "alerts", + "bundle", + "flex", + "laravel", + "lumen", + "messages", + "notifications", + "notify", + "php", + "pnotify", + "symfony", + "toastr", + "yoeunes" + ], + "support": { + "source": "https://github.com/php-flasher/flasher-symfony/tree/v1.7.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/yoeunes", + "type": "custom" + }, + { + "url": "https://ko-fi.com/yoeunes", + "type": "ko_fi" + }, + { + "url": "https://opencollective.com/php-flasher", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/yoeunes", + "type": "patreon" + } + ], + "time": "2022-07-05T19:36:36+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -2910,6 +3423,114 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, { "name": "psr/link", "version": "2.0.1", @@ -3016,6 +3637,50 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "rector/rector", "version": "0.12.23", @@ -8175,6 +8840,269 @@ ], "time": "2022-09-09T09:34:27+00:00" }, + { + "name": "symfony/ux-cropperjs", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-cropperjs.git", + "reference": "123b30ff6b539625bcf50413b945a16de6bc9d6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-cropperjs/zipball/123b30ff6b539625bcf50413b945a16de6bc9d6e", + "reference": "123b30ff6b539625bcf50413b945a16de6bc9d6e", + "shasum": "" + }, + "require": { + "intervention/image": "^2.5", + "php": ">=7.2.5", + "symfony/config": "^4.4.17|^5.0|^6.0", + "symfony/dependency-injection": "^4.4.17|^5.0|^6.0", + "symfony/form": "^4.4.17|^5.0|^6.0", + "symfony/http-kernel": "^4.4.17|^5.0|^6.0", + "symfony/validator": "^4.4.17|^5.0|^6.0" + }, + "conflict": { + "symfony/flex": "<1.13" + }, + "require-dev": { + "symfony/framework-bundle": "^4.4.17|^5.0|^6.0", + "symfony/phpunit-bridge": "^5.2|^6.0", + "symfony/twig-bundle": "^4.4.17|^5.0|^6.0", + "symfony/var-dumper": "^4.4.17|^5.0|^6.0" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "name": "symfony/ux", + "url": "https://github.com/symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\Cropperjs\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Titouan Galopin", + "email": "galopintitouan@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Cropper.js integration for Symfony", + "homepage": "https://symfony.com", + "keywords": [ + "symfony-ux" + ], + "support": { + "source": "https://github.com/symfony/ux-cropperjs/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-01T17:19:11+00:00" + }, + { + "name": "symfony/ux-dropzone", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-dropzone.git", + "reference": "897dbc3a8e61510c3f9c2ee7fee5596eb9cbf1d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-dropzone/zipball/897dbc3a8e61510c3f9c2ee7fee5596eb9cbf1d6", + "reference": "897dbc3a8e61510c3f9c2ee7fee5596eb9cbf1d6", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/config": "^4.4.17|^5.0|^6.0", + "symfony/dependency-injection": "^4.4.17|^5.0|^6.0", + "symfony/form": "^4.4.17|^5.0|^6.0", + "symfony/http-kernel": "^4.4.17|^5.0|^6.0" + }, + "require-dev": { + "symfony/framework-bundle": "^4.4.17|^5.0|^6.0", + "symfony/phpunit-bridge": "^5.2|^6.0", + "symfony/twig-bundle": "^4.4.17|^5.0|^6.0", + "symfony/var-dumper": "^4.4.17|^5.0|^6.0" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "name": "symfony/ux", + "url": "https://github.com/symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\Dropzone\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Titouan Galopin", + "email": "galopintitouan@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "File input dropzones for Symfony Forms", + "homepage": "https://symfony.com", + "keywords": [ + "symfony-ux" + ], + "support": { + "source": "https://github.com/symfony/ux-dropzone/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-09-08T14:28:44+00:00" + }, + { + "name": "symfony/ux-turbo", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-turbo.git", + "reference": "3476bf0030937824137ad0aa1c9797d7c6085882" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/3476bf0030937824137ad0aa1c9797d7c6085882", + "reference": "3476bf0030937824137ad0aa1c9797d7c6085882", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/webpack-encore-bundle": "^1.11" + }, + "conflict": { + "symfony/flex": "<1.13" + }, + "require-dev": { + "doctrine/annotations": "^1.12", + "doctrine/doctrine-bundle": "^2.2", + "doctrine/orm": "^2.8 | 3.0", + "phpstan/phpstan": "^0.12", + "symfony/debug-bundle": "^5.2|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/form": "^5.2|^6.0", + "symfony/framework-bundle": "^5.2|^6.0", + "symfony/mercure-bundle": "^0.3", + "symfony/messenger": "^5.2|^6.0", + "symfony/panther": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.2.1|^6.0", + "symfony/property-access": "^5.2|^6.0", + "symfony/security-core": "^5.2|^6.0", + "symfony/stopwatch": "^5.2|^6.0", + "symfony/twig-bundle": "^5.2|^6.0", + "symfony/web-profiler-bundle": "^5.2|^6.0" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "name": "symfony/ux", + "url": "https://github.com/symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\Turbo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "kevin@dunglas.fr" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Hotwire Turbo integration for Symfony", + "homepage": "https://symfony.com", + "keywords": [ + "hotwire", + "javascript", + "mercure", + "symfony-ux", + "turbo", + "turbo-stream" + ], + "support": { + "source": "https://github.com/symfony/ux-turbo/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-09-08T14:28:44+00:00" + }, { "name": "symfony/validator", "version": "v6.1.7", diff --git a/config/bundles.php b/config/bundles.php index 08dbdfd..56b2619 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -19,4 +19,9 @@ return [ SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true], SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true], Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true], + Symfony\UX\Dropzone\DropzoneBundle::class => ['all' => true], + Symfony\UX\Turbo\TurboBundle::class => ['all' => true], + Flasher\Symfony\FlasherSymfonyBundle::class => ['all' => true], + Flasher\SweetAlert\Symfony\FlasherSweetAlertSymfonyBundle::class => ['all' => true], + Symfony\UX\Cropperjs\CropperjsBundle::class => ['all' => true], ]; diff --git a/config/packages/liip_imagine.yaml b/config/packages/liip_imagine.yaml index 79bd742..1e7c42f 100644 --- a/config/packages/liip_imagine.yaml +++ b/config/packages/liip_imagine.yaml @@ -1,13 +1,15 @@ # Documentation on how to configure the bundle can be found at: https://symfony.com/doc/current/bundles/LiipImagineBundle/basic-usage.html liip_imagine: # valid drivers options include "gd" or "gmagick" or "imagick" - driver: "gd" + driver: "imagick" + twig: + mode: lazy filter_sets: squared_thumbnail_small: filters: thumbnail: - size: [100, 100] + size: [120, 120] mode: outbound allow_upscale: true squared_thumbnail_medium: diff --git a/migrations/Version20221115104149.php b/migrations/Version20221115104149.php new file mode 100644 index 0000000..3ed9d62 --- /dev/null +++ b/migrations/Version20221115104149.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE user ADD tmp_avatar VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE user DROP tmp_avatar'); + } +} diff --git a/package.json b/package.json index 74f8f8f..3a00bb9 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,17 @@ { "devDependencies": { + "@babel/preset-react": "^7.0.0", "@hotwired/stimulus": "^3.0.0", + "@hotwired/turbo": "^7.0.1", "@popperjs/core": "^2.11.6", "@symfony/stimulus-bridge": "^3.0.0", + "@symfony/ux-cropperjs": "file:vendor/symfony/ux-cropperjs/Resources/assets", + "@symfony/ux-dropzone": "file:vendor/symfony/ux-dropzone/Resources/assets", + "@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/Resources/assets", "@symfony/webpack-encore": "^2.0.0", "autoprefixer": "^10.4.7", "core-js": "^3.0.0", + "cropperjs": "^1.5.9", "file-loader": "^6.0.0", "jquery": "^3.6.0", "less-loader": "^10.0.0", @@ -13,6 +19,7 @@ "regenerator-runtime": "^0.13.2", "sass": "^1.50.0", "sass-loader": "^12.0.0", + "sweetalert2": "^11.6.8", "ts-loader": "^9.0.0", "typescript": "^4.6.4", "webpack-manifest-plugin": "^5.0.0", @@ -44,6 +51,7 @@ "postcss-import": "^14.1.0", "postcss-loader": "^7.0.0", "quill": "^1.3.7", - "quill-html-edit-button": "^2.2.12" + "quill-html-edit-button": "^2.2.12", + "toastify-js": "^1.12.0" } } diff --git a/src/Command/CronRunCommand.php b/src/Command/CronRunCommand.php new file mode 100644 index 0000000..8704968 --- /dev/null +++ b/src/Command/CronRunCommand.php @@ -0,0 +1,72 @@ +addArgument('arg1', InputArgument::OPTIONAL, 'Argument description') + ->addOption('option1', null, InputOption::VALUE_NONE, 'Option description') + ; + */ + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $verbose = $input->getOption('verbose'); + + $staleCount = $this->deleteStaleAvatars(); + + if ($verbose) { + if ($staleCount > 0) { + $io->writeln("There were " . $staleCount . " stale avatars."); + } else { + $io->writeln("There were no stale avatars younger than 24 hours."); + } + } + + return Command::SUCCESS; + } + + private function deleteStaleAvatars(): int + { + $avatarDir = dirname(__DIR__, 2) . '/public/uploads/avatars'; + $keepTime = 86400; // 24 hours + $deletedFiles = 0; + + foreach (glob($avatarDir . '/*') as $entry) { + $filectime = filectime($entry); + if ($filectime && $filectime + $keepTime < time()) { + // check if it in use + if ($user = $this->userRepository->findOneBy(['avatar' => basename($entry)])) { + echo basename($entry) . " is in use by: " . $user->getUserIdentifier() . PHP_EOL; + } + unlink($entry); + $deletedFiles++; + } + + } + return $deletedFiles; + } +} diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index d9e0c0d..32ed070 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -92,8 +92,8 @@ class SecurityController extends AbstractController return $this->render(view: '@default/security/registration_finished.html.twig'); } - return $this->render(view: '@default/security/register.html.twig', parameters: [ - 'registrationForm' => $form->createView(), + return $this->renderForm(view: '@default/security/register.html.twig', parameters: [ + 'registrationForm' => $form, ]); } @@ -142,8 +142,8 @@ class SecurityController extends AbstractController ); } - return $this->render(view: '@default/security/forgot_password.html.twig', parameters: [ - 'requestForm' => $form->createView(), + return $this->renderForm(view: '@default/security/forgot_password.html.twig', parameters: [ + 'requestForm' => $form, ]); } @@ -220,8 +220,8 @@ class SecurityController extends AbstractController return $this->redirectToRoute(route: 'app_main'); } - return $this->render(view: '@default/security/reset_password.html.twig', parameters: [ - 'resetForm' => $form->createView(), + return $this->renderForm(view: '@default/security/reset_password.html.twig', parameters: [ + 'resetForm' => $form, ]); } @@ -330,7 +330,7 @@ class SecurityController extends AbstractController #[Route('/security/resend/verify_email', name: 'security_resend_verify_email')] - public function resendVerifyEmail(Request $request, UserRepository $userRepository) + public function resendVerifyEmail(Request $request, UserRepository $userRepository): Response { if ($request->isMethod('POST')) { diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 9f20143..0edddab 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -6,6 +6,8 @@ use App\Entity\User; use App\Form\EditProfileFormType; use App\Repository\UserRepository; use Doctrine\ORM\EntityManagerInterface; +use Flasher\Prime\FlasherInterface; +use Flasher\SweetAlert\Prime\SweetAlertFactory; use Sunrise\Slugger\Slugger; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -24,50 +26,6 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; */ class UserController extends BaseController { - - #[Route(path: '/profile/edit/{username}', name: 'app_profile_edit')] - public function editProfile(Request $request, UserRepository $userRepository, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager, string $username = ''): Response - { - if ($username !== '') { - if ($this->isGranted(attribute: 'ROLE_ADMIN')) { - $user = $userRepository->findOneBy([ - 'username' => $username, - ]); - } else { - throw new AccessDeniedException(message: 'Only admins are allowed to edit foreign profiles.'); - } - } else { - $user = $this->getUser(); - } - - $form = $this->createForm(type: EditProfileFormType::class, data: $user); - $form->handleRequest(request: $request); - - if ($form->isSubmitted() && $form->isValid()) { - $user = $form->getData(); - // if there's a new password, use it - - if ($form->get(name: 'newPassword')->getData()) - $user->setPassword( - password: $userPasswordHasher->hashPassword( - user: $user, - plainPassword: $form->get(name: 'newPassword')->getData() - ) - ); - - $entityManager->persist(entity: $user); - $entityManager->flush(); - - return $this->redirectToRoute(route: 'app_main'); - }; - - return $this->renderForm(view: '@default/user/edit_profile.html.twig', parameters: [ - 'user' => $user, - 'userForm' => $form - ]); - } - - #[Route(path: '/profile/{username}', name: 'app_profile')] public function showProfile(UserRepository $userRepository, string $username = ''): Response { @@ -85,6 +43,61 @@ class UserController extends BaseController ]); } + #[Route(path: '/profile/edit/{username}', name: 'app_profile_edit')] + public function editProfile(Request $request, + UserRepository $userRepository, + UserPasswordHasherInterface $userPasswordHasher, + EntityManagerInterface $entityManager, + string $username = ''): Response + { + $user = $this->getUser(); + + $editUser = $userRepository->findOneBy(['username' => $username]); + + if ($username !== $editUser->getUsername()) { + if (!$this->isGranted(attribute: 'ROLE_ADMIN')) { + $this->addFlash(type: 'error', message: 'Only admins are allowed to edit foreign profiles.'); + return $this->redirectToRoute(route: 'app_main'); + + } + } + + $form = $this->createForm(type: EditProfileFormType::class, data: $user); + $form->handleRequest(request: $request); + + if ($form->isSubmitted() && $form->isValid()) { + $user = $form->getData(); + // if there's a new password, use it + + if ($form->get(name: 'newPassword')->getData()) { + $user->setPassword( + password: $userPasswordHasher->hashPassword( + user: $user, + plainPassword: $form->get(name: 'newPassword')->getData() + ) + ); + } + + if ($user->getTmpAvatar()) { + $user->setAvatar($user->getTmpAvatar()); + $user->setTmpAvatar(''); + } + + $entityManager->persist(entity: $user); + $entityManager->flush(); + + $this->addFlash(type: 'success', message: 'Profile successfully updated.'); + + return $this->redirectToRoute(route: 'app_main'); + }; + + return $this->renderForm(view: '@default/user/edit_profile.html.twig', parameters: [ + 'user' => $editUser, + 'userForm' => $form + ]); + } + + #[Route(path: '/list_users/', name: 'app_list_user')] public function listUsers(UserRepository $userRepository): Response { @@ -96,7 +109,7 @@ class UserController extends BaseController } // TODO move to a helper class - function humanFilesize($bytes, $decimals = 2) + function humanFilesize($bytes, $decimals = 2): string { $sz = 'BKMGTP'; $factor = floor((strlen($bytes) - 1) / 3); @@ -132,7 +145,8 @@ class UserController extends BaseController $cleanFilename = $slugger->slugify($originalFilename); $newFilename = $cleanFilename . '-' . uniqid() . '.' . $uploadedAvatar->guessExtension(); $uploadedAvatar->move($destination, $newFilename); - $user->setAvatar($newFilename); + // Store the tmp name, use it on real form submit. + $user->setTmpAvatar($newFilename); $entityManager->persist(entity: $user); $entityManager->flush(); diff --git a/src/Entity/User.php b/src/Entity/User.php index 8540453..1b11469 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -64,6 +64,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column] private ?DateTimeImmutable $agreedTermsAt = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $tmpAvatar = null; + public function __construct() { $this->projects = new ArrayCollection(); @@ -315,4 +318,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface { // TODO: Implement eraseCredentials() method. } + + public function getTmpAvatar(): ?string + { + return $this->tmpAvatar; + } + + public function setTmpAvatar(?string $tmpAvatar): self + { + $this->tmpAvatar = $tmpAvatar; + + return $this; + } } diff --git a/src/Form/EditProfileFormType.php b/src/Form/EditProfileFormType.php index a60f68b..5ba3b9c 100644 --- a/src/Form/EditProfileFormType.php +++ b/src/Form/EditProfileFormType.php @@ -5,34 +5,65 @@ namespace App\Form; use App\Entity\User; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\EmailType; -use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Validator\Constraints\Image; use Symfony\Component\Validator\Constraints\Length; +use Symfony\UX\Cropperjs\Form\CropperType; +use Symfony\UX\Dropzone\Form\DropzoneType; class EditProfileFormType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder - ->add(child: 'username') - ->add(child: 'firstName') - ->add(child: 'lastName') - ->add(child: 'email', type: EmailType::class) + ->add(child: 'username', options: [ + 'attr' => [ + 'autocomplete' => 'nickname' + ] + ]) + ->add(child: 'firstName', options: [ + 'required' => false, + 'attr' => [ + 'autocomplete' => 'given-name' + ] + ]) + ->add(child: 'lastName', options: [ + 'required' => false, + 'attr' => [ + 'autocomplete' => 'family-name' + ] + + ]) + ->add(child: 'email', type: EmailType::class, options: [ + 'attr' => [ + 'autocomplete' => 'email' + ] + ]) + /* + ->add(child: 'avatarName', type: DropzoneType::class, options: [ + 'mapped' => false, + 'required' => false, + 'attr' => [ + 'data-controller' => 'upload-avatar', + ] + ]) + */ ->add(child: 'newPassword', type: RepeatedType::class, options: [ 'type' => PasswordType::class, 'mapped' => false, 'invalid_message' => 'The password fields must match.', - 'options' => ['attr' => ['class' => 'password-field', 'autocomplete' => 'off']], 'required' => false, 'first_options' => ['label' => 'Password'], 'second_options' => ['label' => 'Repeat Password (only needed if you want to update the password)'], - 'constraints' => [new Length(exactly: ['min' => 6])] - ]) - ; + 'constraints' => [new Length(exactly: ['min' => 6])], + 'options' => [ + 'attr' => [ + 'autocomplete' => 'new-password' + ] + ] + ]); } public function configureOptions(OptionsResolver $resolver): void diff --git a/symfony.lock b/symfony.lock index 687a9a1..8fceaae 100644 --- a/symfony.lock +++ b/symfony.lock @@ -72,6 +72,12 @@ "config/packages/nelmio_cors.yaml" ] }, + "php-flasher/flasher-sweetalert-symfony": { + "version": "v1.7.0" + }, + "php-flasher/flasher-symfony": { + "version": "v1.7.0" + }, "sensio/framework-extra-bundle": { "version": "6.2", "recipe": { @@ -271,6 +277,15 @@ "templates/base.html.twig" ] }, + "symfony/ux-cropperjs": { + "version": "v2.5.0" + }, + "symfony/ux-dropzone": { + "version": "v2.5.0" + }, + "symfony/ux-turbo": { + "version": "v2.5.0" + }, "symfony/validator": { "version": "6.1", "recipe": { diff --git a/templates/themes/default/_footer.html.twig b/templates/themes/24unix.net/_footer.html.twig similarity index 100% rename from templates/themes/default/_footer.html.twig rename to templates/themes/24unix.net/_footer.html.twig diff --git a/templates/themes/default/_header.html.twig b/templates/themes/24unix.net/_header.html.twig similarity index 80% rename from templates/themes/default/_header.html.twig rename to templates/themes/24unix.net/_header.html.twig index de4e893..b99ea83 100644 --- a/templates/themes/default/_header.html.twig +++ b/templates/themes/24unix.net/_header.html.twig @@ -1,4 +1,6 @@ -